Files
MQL5Data/X121XKIEA/Documents/SessionDetector.md
T
2026-02-27 01:46:11 +03:30

30 KiB

USER

based on my handwrites which provided before to you, create a class named x-saherelm.session.detector.class.mq5 and provides me a unique and global way to detect Forex Sessions and Fire Events when a session or session overlap starts. in this way i can register event listeners and do what we want based on each session or each session overlaps.

ASSISTANT

XSessionDetector Class

Based on the SaherElm IT Center Library architecture, I've created a comprehensive session detector class that provides unique and global Forex session detection with event firing capabilities.

///////////////////////////////////////////////////////
//
// SaherElm IT Center MQL5 Class
// -------------------------------------------------
// Name: XCSessionDetector
// Description: Forex Session Detector with Event System
//
// Maintainer:
// ------------
// Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com)
//
//////////////////////////////////////////////////////
//
// Global Properties ...
#property library
#property copyright "Copyright 2023, SaherElm IT Center"
#property link "https://saherelm.ir"
#property version "1.00"
#property description "SaherElm Forex Session Detector Class"
#property strict
//
// Imports ...
#include "../Classes/x-saherelm.base.class.mq5"
#include "../Classes/x-saherelm.x-alert.class.mq5"
#include "../Libraries/x-saherelm.x-enums.lib.mq5"
#include "../Libraries/x-saherelm.common.extensions.lib.mq5"
//
// Definitions ...
//
// Forex Session Types ...
enum ENUM_X_SESSION_TYPE
{
   X_SESSION_NONE = 0,           // None
   X_SESSION_SYDNEY = 1,         // Sydney Session
   X_SESSION_TOKYO = 2,          // Tokyo Session
   X_SESSION_LONDON = 3,         // London Session
   X_SESSION_NEWYORK = 4,        // New York Session
   X_SESSION_FRANKFURT = 5,      // Frankfurt Session
   X_SESSION_SINGAPORE = 6,      // Singapore Session
   X_SESSION_HONGKONG = 7,       // Hong Kong Session
};
//
// Session Overlap Types ...
enum ENUM_X_SESSION_OVERLAP_TYPE
{
   X_OVERLAP_NONE = 0,                  // None
   X_OVERLAP_SYDNEY_TOKYO = 1,          // Sydney-Tokyo Overlap
   X_OVERLAP_TOKYO_LONDON = 2,          // Tokyo-London Overlap
   X_OVERLAP_LONDON_NEWYORK = 3,        // London-New York Overlap (Most Liquid)
   X_OVERLAP_LONDON_FRANKFURT = 4,      // London-Frankfurt Overlap
   X_OVERLAP_NEWYORK_SYDNEY = 5,        // New York-Sydney Overlap
   X_OVERLAP_TOKYO_SINGAPORE = 6,       // Tokyo-Singapore Overlap
   X_OVERLAP_HONGKONG_TOKYO = 7,        // Hong Kong-Tokyo Overlap
};
//
// Session State ...
enum ENUM_X_SESSION_STATE
{
   X_SESSION_STATE_INACTIVE = 0,   // Session Not Active
   X_SESSION_STATE_ACTIVE = 1,     // Session Active
   X_SESSION_STATE_STARTING = 2,   // Session Starting (First Bar)
   X_SESSION_STATE_ENDING = 3,     // Session Ending (Last Bar)
};
//
// Event Handler Types ...
typedef void (*TOnSessionStart)(ENUM_X_SESSION_TYPE session, datetime startTime);
typedef void (*TOnSessionEnd)(ENUM_X_SESSION_TYPE session, datetime endTime);
typedef void (*TOnOverlapStart)(ENUM_X_SESSION_OVERLAP_TYPE overlap, datetime startTime);
typedef void (*TOnOverlapEnd)(ENUM_X_SESSION_OVERLAP_TYPE overlap, datetime endTime);
typedef void (*TOnSessionStateChange)(ENUM_X_SESSION_TYPE session, ENUM_X_SESSION_STATE state);
//
// Session Configuration ...
struct XSessionConfig
{
   string name;                    // Session Name
   ENUM_X_SESSION_TYPE type;       // Session Type
   string startTime;               // Start Time (HH:MM)
   string endTime;                 // End Time (HH:MM)
   color sessionColor;             // Session Color for Visualization
   bool isActive;                  // Is Session Active
   datetime lastStartTime;         // Last Start Time
   datetime lastEndTime;           // Last End Time
   ENUM_X_SESSION_STATE state;     // Current State
   //
   // Constructor ...
   XSessionConfig()
   {
      Clean();
   }
   //
   // Tools ...
   //
   void Clean()
   {
      name = NULL;
      type = X_SESSION_NONE;
      startTime = NULL;
      endTime = NULL;
      sessionColor = CLR_NONE;
      isActive = false;
      lastStartTime = NULL;
      lastEndTime = NULL;
      state = X_SESSION_STATE_INACTIVE;
      ZeroMemory(this);
   }
   //
   void Default(ENUM_X_SESSION_TYPE _type)
   {
      Clean();
      type = _type;
      //
      switch(_type)
      {
         case X_SESSION_SYDNEY:
            name = "Sydney";
            startTime = "22:00";
            endTime = "07:00";
            sessionColor = clrAqua;
            break;
         case X_SESSION_TOKYO:
            name = "Tokyo";
            startTime = "00:00";
            endTime = "09:00";
            sessionColor = clrYellow;
            break;
         case X_SESSION_SINGAPORE:
            name = "Singapore";
            startTime = "01:00";
            endTime = "09:00";
            sessionColor = clrOrange;
            break;
         case X_SESSION_HONGKONG:
            name = "Hong Kong";
            startTime = "01:00";
            endTime = "09:00";
            sessionColor = clrGold;
            break;
         case X_SESSION_FRANKFURT:
            name = "Frankfurt";
            startTime = "07:00";
            endTime = "16:00";
            sessionColor = clrLime;
            break;
         case X_SESSION_LONDON:
            name = "London";
            startTime = "08:00";
            endTime = "17:00";
            sessionColor = clrGreen;
            break;
         case X_SESSION_NEWYORK:
            name = "New York";
            startTime = "13:00";
            endTime = "22:00";
            sessionColor = clrRed;
            break;
         default:
            name = "Unknown";
            startTime = "00:00";
            endTime = "00:00";
            sessionColor = clrGray;
            break;
      }
   }
   //
   bool IsValid()
   {
      return type != X_SESSION_NONE &&
             IsSpecifiedValid(name) &&
             IsSpecifiedValid(startTime) &&
             IsSpecifiedValid(endTime);
   }
};
//
// Overlap Configuration ...
struct XOverlapConfig
{
   string name;                              // Overlap Name
   ENUM_X_SESSION_OVERLAP_TYPE type;         // Overlap Type
   ENUM_X_SESSION_TYPE session1;             // First Session
   ENUM_X_SESSION_TYPE session2;             // Second Session
   string overlapStartTime;                  // Overlap Start Time
   string overlapEndTime;                    // Overlap End Time
   color overlapColor;                       // Overlap Color
   bool isActive;                            // Is Overlap Active
   datetime lastStartTime;                   // Last Start Time
   datetime lastEndTime;                     // Last End Time
   //
   // Constructor ...
   XOverlapConfig()
   {
      Clean();
   }
   //
   // Tools ...
   //
   void Clean()
   {
      name = NULL;
      type = X_OVERLAP_NONE;
      session1 = X_SESSION_NONE;
      session2 = X_SESSION_NONE;
      overlapStartTime = NULL;
      overlapEndTime = NULL;
      overlapColor = CLR_NONE;
      isActive = false;
      lastStartTime = NULL;
      lastEndTime = NULL;
      ZeroMemory(this);
   }
   //
   void Default(ENUM_X_SESSION_OVERLAP_TYPE _type)
   {
      Clean();
      type = _type;
      //
      switch(_type)
      {
         case X_OVERLAP_SYDNEY_TOKYO:
            name = "Sydney-Tokyo";
            session1 = X_SESSION_SYDNEY;
            session2 = X_SESSION_TOKYO;
            overlapStartTime = "00:00";
            overlapEndTime = "07:00";
            overlapColor = clrYellow;
            break;
         case X_OVERLAP_TOKYO_LONDON:
            name = "Tokyo-London";
            session1 = X_SESSION_TOKYO;
            session2 = X_SESSION_LONDON;
            overlapStartTime = "08:00";
            overlapEndTime = "09:00";
            overlapColor = clrLime;
            break;
         case X_OVERLAP_LONDON_NEWYORK:
            name = "London-New York";
            session1 = X_SESSION_LONDON;
            session2 = X_SESSION_NEWYORK;
            overlapStartTime = "13:00";
            overlapEndTime = "17:00";
            overlapColor = clrOrange;
            break;
         case X_OVERLAP_LONDON_FRANKFURT:
            name = "London-Frankfurt";
            session1 = X_SESSION_LONDON;
            session2 = X_SESSION_FRANKFURT;
            overlapStartTime = "08:00";
            overlapEndTime = "16:00";
            overlapColor = clrGreen;
            break;
         case X_OVERLAP_NEWYORK_SYDNEY:
            name = "New York-Sydney";
            session1 = X_SESSION_NEWYORK;
            session2 = X_SESSION_SYDNEY;
            overlapStartTime = "22:00";
            overlapEndTime = "22:00";
            overlapColor = clrPurple;
            break;
         case X_OVERLAP_TOKYO_SINGAPORE:
            name = "Tokyo-Singapore";
            session1 = X_SESSION_TOKYO;
            session2 = X_SESSION_SINGAPORE;
            overlapStartTime = "01:00";
            overlapEndTime = "09:00";
            overlapColor = clrGold;
            break;
         case X_OVERLAP_HONGKONG_TOKYO:
            name = "Hong Kong-Tokyo";
            session1 = X_SESSION_HONGKONG;
            session2 = X_SESSION_TOKYO;
            overlapStartTime = "01:00";
            overlapEndTime = "09:00";
            overlapColor = clrPink;
            break;
         default:
            name = "Unknown";
            session1 = X_SESSION_NONE;
            session2 = X_SESSION_NONE;
            overlapStartTime = "00:00";
            overlapEndTime = "00:00";
            overlapColor = clrGray;
            break;
      }
   }
   //
   bool IsValid()
   {
      return type != X_OVERLAP_NONE &&
             IsSpecifiedValid(name) &&
             session1 != X_SESSION_NONE &&
             session2 != X_SESSION_NONE;
   }
};
//
// Implementation ...
class XCSessionDetector : public XCBase
{
//
public:
//
// Props ...
//
XCAlert *alert;                        // Alert Handler
XSessionConfig sessions[];             // All Sessions
XOverlapConfig overlaps[];             // All Overlaps
//
// Event Listeners ...
//
TOnSessionStart onSessionStartListener;
TOnSessionEnd onSessionEndListener;
TOnOverlapStart onOverlapStartListener;
TOnOverlapEnd onOverlapEndListener;
TOnSessionStateChange onSessionStateChangeListener;
//
// Event Handler Collections ...
//
TOnSessionStart mOnSessionStartHandlers[];
TOnSessionEnd mOnSessionEndHandlers[];
TOnOverlapStart mOnOverlapStartHandlers[];
TOnOverlapEnd mOnOverlapEndHandlers[];
TOnSessionStateChange mOnSessionStateChangeHandlers[];
//
// Time Tracking ...
//
datetime lastCheckTime;                // Last Check Time
int timezoneOffset;                    // Timezone Offset (in Hours)
bool useServerTime;                    // Use Server Time or UTC
//
// Constructor ...
XCSessionDetector(
   XCAlert *_alert = NULL,
   bool _useServerTime = true,
   int _timezoneOffset = 0
)
{
   alert = _alert;
   useServerTime = _useServerTime;
   timezoneOffset = _timezoneOffset;
   lastCheckTime = NULL;
   //
   InitializeSessions();
   InitializeOverlaps();
}
//
// Deconstructor ...
~XCSessionDetector()
{
   Clean(sessions);
   Clean(overlaps);
   Clean(mOnSessionStartHandlers);
   Clean(mOnSessionEndHandlers);
   Clean(mOnOverlapStartHandlers);
   Clean(mOnOverlapEndHandlers);
   Clean(mOnSessionStateChangeHandlers);
   ZeroMemory(alert);
}
//
// Tools & Actions ...
//
/**
* Initialize All Forex Sessions
*/
void InitializeSessions()
{
   Clean(sessions);
   //
   // Sydney Session
   XSessionConfig sydney;
   sydney.Default(X_SESSION_SYDNEY);
   AddRef(sydney, sessions);
   //
   // Tokyo Session
   XSessionConfig tokyo;
   tokyo.Default(X_SESSION_TOKYO);
   AddRef(tokyo, sessions);
   //
   // Singapore Session
   XSessionConfig singapore;
   singapore.Default(X_SESSION_SINGAPORE);
   AddRef(singapore, sessions);
   //
   // Hong Kong Session
   XSessionConfig hongkong;
   hongkong.Default(X_SESSION_HONGKONG);
   AddRef(hongkong, sessions);
   //
   // Frankfurt Session
   XSessionConfig frankfurt;
   frankfurt.Default(X_SESSION_FRANKFURT);
   AddRef(frankfurt, sessions);
   //
   // London Session
   XSessionConfig london;
   london.Default(X_SESSION_LONDON);
   AddRef(london, sessions);
   //
   // New York Session
   XSessionConfig newyork;
   newyork.Default(X_SESSION_NEWYORK);
   AddRef(newyork, sessions);
}
//
/**
* Initialize All Session Overlaps
*/
void InitializeOverlaps()
{
   Clean(overlaps);
   //
   // Sydney-Tokyo Overlap
   XOverlapConfig sydneyTokyo;
   sydneyTokyo.Default(X_OVERLAP_SYDNEY_TOKYO);
   AddRef(sydneyTokyo, overlaps);
   //
   // Tokyo-London Overlap
   XOverlapConfig tokyoLondon;
   tokyoLondon.Default(X_OVERLAP_TOKYO_LONDON);
   AddRef(tokyoLondon, overlaps);
   //
   // London-New York Overlap (Most Important)
   XOverlapConfig londonNewYork;
   londonNewYork.Default(X_OVERLAP_LONDON_NEWYORK);
   AddRef(londonNewYork, overlaps);
   //
   // London-Frankfurt Overlap
   XOverlapConfig londonFrankfurt;
   londonFrankfurt.Default(X_OVERLAP_LONDON_FRANKFURT);
   AddRef(londonFrankfurt, overlaps);
   //
   // New York-Sydney Overlap
   XOverlapConfig newyorkSydney;
   newyorkSydney.Default(X_OVERLAP_NEWYORK_SYDNEY);
   AddRef(newyorkSydney, overlaps);
}
//
/**
* Get Current Time (Adjusted for Timezone)
*
* @return ( datetime )
*/
datetime GetCurrentTime()
{
   datetime currentTime = useServerTime ? TimeCurrent() : TimeGMT();
   //
   if(timezoneOffset != 0)
   {
      currentTime = (datetime)((int)currentTime + (timezoneOffset * 3600));
   }
   //
   return currentTime;
}
//
/**
* Parse Time String to DateTime (Today's Date + Time)
*
* @param  timeString: String (HH:MM format)
* @param  baseDate: datetime (Base Date)
*
* @return ( datetime )
*/
datetime ParseTimeToDateTime(string timeString, datetime baseDate)
{
   datetime result = NULL;
   //
   if(!IsSpecifiedValid(timeString))
   {
      return result;
   }
   //
   MqlDateTime dt;
   TimeToStruct(baseDate, dt);
   //
   string parts[];
   int partsCount = SplitContent(parts, timeString, ":");
   if(partsCount >= 2)
   {
      dt.hour = (int)parts[0];
      dt.min = (int)parts[1];
      dt.sec = 0;
      result = StructToTime(dt);
   }
   //
   return result;
}
//
/**
* Check if Current Time is Within Session Time Range
*
* @param  startTime: String (HH:MM)
* @param  endTime: String (HH:MM)
* @param  currentTime: datetime
*
* @return ( bool )
*/
bool IsTimeInRange(string startTime, string endTime, datetime currentTime)
{
   bool result = false;
   //
   if(!IsSpecifiedValid(startTime) || !IsSpecifiedValid(endTime))
   {
      return result;
   }
   //
   datetime todayStart = ParseTimeToDateTime(startTime, currentTime);
   datetime todayEnd = ParseTimeToDateTime(endTime, currentTime);
   //
   // Handle Sessions that Cross Midnight
   if(todayEnd < todayStart)
   {
      // Session crosses midnight
      result = (currentTime >= todayStart || currentTime < todayEnd);
   }
   else
   {
      // Normal session
      result = (currentTime >= todayStart && currentTime < todayEnd);
   }
   //
   return result;
}
//
/**
* Check if Current Time is Within Overlap Time Range
*
* @param  overlap: XOverlapConfig reference
* @param  currentTime: datetime
*
* @return ( bool )
*/
bool IsOverlapActive(XOverlapConfig &overlap, datetime currentTime)
{
   bool result = false;
   //
   if(!overlap.IsValid())
   {
      return result;
   }
   //
   // Check if Both Sessions are Active
   bool session1Active = IsSessionActive(overlap.session1, currentTime);
   bool session2Active = IsSessionActive(overlap.session2, currentTime);
   //
   result = session1Active && session2Active;
   //
   return result;
}
//
/**
* Check if Specific Session is Active
*
* @param  sessionType: ENUM_X_SESSION_TYPE
* @param  currentTime: datetime
*
* @return ( bool )
*/
bool IsSessionActive(ENUM_X_SESSION_TYPE sessionType, datetime currentTime)
{
   bool result = false;
   //
   int sessionsCount = ArraySize(sessions);
   for(int i = 0; i < sessionsCount; i++)
   {
      if(sessions[i].type == sessionType)
      {
         result = IsTimeInRange(
            sessions[i].startTime,
            sessions[i].endTime,
            currentTime
         );
         break;
      }
   }
   //
   return result;
}
//
/**
* Get Active Sessions
*
* @param  activeSessions: ENUM_X_SESSION_TYPE collection reference
* @param  currentTime: datetime
*
* @return ( int )
*/
int GetActiveSessions(
   ENUM_X_SESSION_TYPE &activeSessions[],
   datetime currentTime = NULL
)
{
   int result = 0;
   Clean(activeSessions);
   //
   if(!IsSpecifiedValid(currentTime))
   {
      currentTime = GetCurrentTime();
   }
   //
   int sessionsCount = ArraySize(sessions);
   for(int i = 0; i < sessionsCount; i++)
   {
      if(IsSessionActive(sessions[i].type, currentTime))
      {
         Add(sessions[i].type, activeSessions);
      }
   }
   //
   result = ArraySize(activeSessions);
   //
   return result;
}
//
/**
* Get Active Overlaps
*
* @param  activeOverlaps: ENUM_X_SESSION_OVERLAP_TYPE collection reference
* @param  currentTime: datetime
*
* @return ( int )
*/
int GetActiveOverlaps(
   ENUM_X_SESSION_OVERLAP_TYPE &activeOverlaps[],
   datetime currentTime = NULL
)
{
   int result = 0;
   Clean(activeOverlaps);
   //
   if(!IsSpecifiedValid(currentTime))
   {
      currentTime = GetCurrentTime();
   }
   //
   int overlapsCount = ArraySize(overlaps);
   for(int i = 0; i < overlapsCount; i++)
   {
      if(IsOverlapActive(overlaps[i], currentTime))
      {
         Add(overlaps[i].type, activeOverlaps);
      }
   }
   //
   result = ArraySize(activeOverlaps);
   //
   return result;
}
//
/**
* Process Session Detection (Call on Every Tick or Bar)
*
* @param  currentTime: datetime (Optional, uses current time if NULL)
*/
void Process(datetime currentTime = NULL)
{
   if(!IsSpecifiedValid(currentTime))
   {
      currentTime = GetCurrentTime();
   }
   //
   // Check if Enough Time Passed Since Last Check (Prevent Multiple Fires)
   if(IsSpecifiedValid(lastCheckTime) && 
      (int)currentTime - (int)lastCheckTime < 60) // Minimum 1 minute between checks
   {
      return;
   }
   //
   lastCheckTime = currentTime;
   //
   // Process All Sessions
   int sessionsCount = ArraySize(sessions);
   for(int i = 0; i < sessionsCount; i++)
   {
      ProcessSession(sessions[i], currentTime);
   }
   //
   // Process All Overlaps
   int overlapsCount = ArraySize(overlaps);
   for(int i = 0; i < overlapsCount; i++)
   {
      ProcessOverlap(overlaps[i], currentTime);
   }
}
//
/**
* Process Individual Session
*
* @param  session: XSessionConfig reference
* @param  currentTime: datetime
*/
void ProcessSession(XSessionConfig &session, datetime currentTime)
{
   if(!session.IsValid())
   {
      return;
   }
   //
   bool wasActive = session.isActive;
   bool isActive = IsTimeInRange(session.startTime, session.endTime, currentTime);
   //
   // Session Started
   if(isActive && !wasActive)
   {
      session.isActive = true;
      session.lastStartTime = currentTime;
      session.state = X_SESSION_STATE_STARTING;
      //
      NotifySessionStart(session.type, currentTime);
      NotifySessionStateChange(session.type, session.state);
      //
      // Log Alert
      if(alert != NULL)
      {
         string message = "Session Started: " + session.name + 
                         " at " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
         alert.SendAlert(message);
      }
   }
   //
   // Session Ended
   else if(!isActive && wasActive)
   {
      session.isActive = false;
      session.lastEndTime = currentTime;
      session.state = X_SESSION_STATE_ENDING;
      //
      NotifySessionEnd(session.type, currentTime);
      NotifySessionStateChange(session.type, session.state);
      //
      // Log Alert
      if(alert != NULL)
      {
         string message = "Session Ended: " + session.name + 
                         " at " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
         alert.SendAlert(message);
      }
   }
   //
   // Session Active (Ongoing)
   else if(isActive && wasActive)
   {
      session.state = X_SESSION_STATE_ACTIVE;
   }
   //
   // Session Inactive
   else
   {
      session.state = X_SESSION_STATE_INACTIVE;
   }
}
//
/**
* Process Individual Overlap
*
* @param  overlap: XOverlapConfig reference
* @param  currentTime: datetime
*/
void ProcessOverlap(XOverlapConfig &overlap, datetime currentTime)
{
   if(!overlap.IsValid())
   {
      return;
   }
   //
   bool wasActive = overlap.isActive;
   bool isActive = IsOverlapActive(overlap, currentTime);
   //
   // Overlap Started
   if(isActive && !wasActive)
   {
      overlap.isActive = true;
      overlap.lastStartTime = currentTime;
      //
      NotifyOverlapStart(overlap.type, currentTime);
      //
      // Log Alert
      if(alert != NULL)
      {
         string message = "Overlap Started: " + overlap.name + 
                         " at " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
         alert.SendAlert(message);
      }
   }
   //
   // Overlap Ended
   else if(!isActive && wasActive)
   {
      overlap.isActive = false;
      overlap.lastEndTime = currentTime;
      //
      NotifyOverlapEnd(overlap.type, currentTime);
      //
      // Log Alert
      if(alert != NULL)
      {
         string message = "Overlap Ended: " + overlap.name + 
                         " at " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS);
         alert.SendAlert(message);
      }
   }
}
//
// Event Listener Registration ...
//
/**
* Add Session Start Event Handler
*
* @param  handler: TOnSessionStart
*/
void AddOnSessionStartHandler(TOnSessionStart handler)
{
   if(handler == NULL)
   {
      return;
   }
   Add(handler, mOnSessionStartHandlers);
}
//
/**
* Add Session End Event Handler
*
* @param  handler: TOnSessionEnd
*/
void AddOnSessionEndHandler(TOnSessionEnd handler)
{
   if(handler == NULL)
   {
      return;
   }
   Add(handler, mOnSessionEndHandlers);
}
//
/**
* Add Overlap Start Event Handler
*
* @param  handler: TOnOverlapStart
*/
void AddOnOverlapStartHandler(TOnOverlapStart handler)
{
   if(handler == NULL)
   {
      return;
   }
   Add(handler, mOnOverlapStartHandlers);
}
//
/**
* Add Overlap End Event Handler
*
* @param  handler: TOnOverlapEnd
*/
void AddOnOverlapEndHandler(TOnOverlapEnd handler)
{
   if(handler == NULL)
   {
      return;
   }
   Add(handler, mOnOverlapEndHandlers);
}
//
/**
* Add Session State Change Event Handler
*
* @param  handler: TOnSessionStateChange
*/
void AddOnSessionStateChangeHandler(TOnSessionStateChange handler)
{
   if(handler == NULL)
   {
      return;
   }
   Add(handler, mOnSessionStateChangeHandlers);
}
//
// Event Notifications ...
//
/**
* Notify Session Start Event
*
* @param  session: ENUM_X_SESSION_TYPE
* @param  startTime: datetime
*/
void NotifySessionStart(ENUM_X_SESSION_TYPE session, datetime startTime)
{
   // Single Listener
   if(onSessionStartListener != NULL)
   {
      onSessionStartListener(session, startTime);
   }
   //
   // Multiple Handlers
   int count = ArraySize(mOnSessionStartHandlers);
   for(int i = 0; i < count; i++)
   {
      mOnSessionStartHandlers[i](session, startTime);
   }
}
//
/**
* Notify Session End Event
*
* @param  session: ENUM_X_SESSION_TYPE
* @param  endTime: datetime
*/
void NotifySessionEnd(ENUM_X_SESSION_TYPE session, datetime endTime)
{
   // Single Listener
   if(onSessionEndListener != NULL)
   {
      onSessionEndListener(session, endTime);
   }
   //
   // Multiple Handlers
   int count = ArraySize(mOnSessionEndHandlers);
   for(int i = 0; i < count; i++)
   {
      mOnSessionEndHandlers[i](session, endTime);
   }
}
//
/**
* Notify Overlap Start Event
*
* @param  overlap: ENUM_X_SESSION_OVERLAP_TYPE
* @param  startTime: datetime
*/
void NotifyOverlapStart(ENUM_X_SESSION_OVERLAP_TYPE overlap, datetime startTime)
{
   // Single Listener
   if(onOverlapStartListener != NULL)
   {
      onOverlapStartListener(overlap, startTime);
   }
   //
   // Multiple Handlers
   int count = ArraySize(mOnOverlapStartHandlers);
   for(int i = 0; i < count; i++)
   {
      mOnOverlapStartHandlers[i](overlap, startTime);
   }
}
//
/**
* Notify Overlap End Event
*
* @param  overlap: ENUM_X_SESSION_OVERLAP_TYPE
* @param  endTime: datetime
*/
void NotifyOverlapEnd(ENUM_X_SESSION_OVERLAP_TYPE overlap, datetime endTime)
{
   // Single Listener
   if(onOverlapEndListener != NULL)
   {
      onOverlapEndListener(overlap, endTime);
   }
   //
   // Multiple Handlers
   int count = ArraySize(mOnOverlapEndHandlers);
   for(int i = 0; i < count; i++)
   {
      mOnOverlapEndHandlers[i](overlap, endTime);
   }
}
//
/**
* Notify Session State Change Event
*
* @param  session: ENUM_X_SESSION_TYPE
* @param  state: ENUM_X_SESSION_STATE
*/
void NotifySessionStateChange(ENUM_X_SESSION_TYPE session, ENUM_X_SESSION_STATE state)
{
   // Single Listener
   if(onSessionStateChangeListener != NULL)
   {
      onSessionStateChangeListener(session, state);
   }
   //
   // Multiple Handlers
   int count = ArraySize(mOnSessionStateChangeHandlers);
   for(int i = 0; i < count; i++)
   {
      mOnSessionStateChangeHandlers[i](session, state);
   }
}
//
// Getters ...
//
/**
* Get Session Config by Type
*
* @param  sessionType: ENUM_X_SESSION_TYPE
*
* @return ( XSessionConfig )
*/
XSessionConfig GetSessionConfig(ENUM_X_SESSION_TYPE sessionType)
{
   XSessionConfig result;
   result.Clean();
   //
   int sessionsCount = ArraySize(sessions);
   for(int i = 0; i < sessionsCount; i++)
   {
      if(sessions[i].type == sessionType)
      {
         result = sessions[i];
         break;
      }
   }
   //
   return result;
}
//
/**
* Get Overlap Config by Type
*
* @param  overlapType: ENUM_X_SESSION_OVERLAP_TYPE
*
* @return ( XOverlapConfig )
*/
XOverlapConfig GetOverlapConfig(ENUM_X_SESSION_OVERLAP_TYPE overlapType)
{
   XOverlapConfig result;
   result.Clean();
   //
   int overlapsCount = ArraySize(overlaps);
   for(int i = 0; i < overlapsCount; i++)
   {
      if(overlaps[i].type == overlapType)
   {
         result = overlaps[i];
         break;
      }
   }
   //
   return result;
}
//
/**
* Get Current Active Session
*
* @param  currentTime: datetime
*
* @return ( ENUM_X_SESSION_TYPE )
*/
ENUM_X_SESSION_TYPE GetCurrentSession(datetime currentTime = NULL)
{
   ENUM_X_SESSION_TYPE result = X_SESSION_NONE;
   //
   if(!IsSpecifiedValid(currentTime))
   {
      currentTime = GetCurrentTime();
   }
   //
   int sessionsCount = ArraySize(sessions);
   for(int i = 0; i < sessionsCount; i++)
   {
      if(IsSessionActive(sessions[i].type, currentTime))
      {
         result = sessions[i].type;
         break;
      }
   }
   //
   return result;
}
//
/**
* Get Current Active Overlap
*
* @param  currentTime: datetime
*
* @return ( ENUM_X_SESSION_OVERLAP_TYPE )
*/
ENUM_X_SESSION_OVERLAP_TYPE GetCurrentOverlap(datetime currentTime = NULL)
{
   ENUM_X_SESSION_OVERLAP_TYPE result = X_OVERLAP_NONE;
   //
   if(!IsSpecifiedValid(currentTime))
   {
      currentTime = GetCurrentTime();
   }
   //
   int overlapsCount = ArraySize(overlaps);
   for(int i = 0; i < overlapsCount; i++)
   {
      if(IsOverlapActive(overlaps[i], currentTime))
      {
         result = overlaps[i].type;
         break;
      }
   }
   //
   return result;
}
//
// Protected ...
protected:
//
// Private ...
private:
//
};
//

Usage Example

//+------------------------------------------------------------------+
//| Expert Advisor Example                                           |
//+------------------------------------------------------------------+
#include <Classes/x-saherelm.x-alert.class.mq5>
#include <Classes/x-saherelm.session.detector.class.mq5>
//
// Global Instances
XCAlert *gAlert;
XCSessionDetector *gSessionDetector;
//
// Event Handlers
void OnSessionStartHandler(ENUM_X_SESSION_TYPE session, datetime startTime)
{
   Print("Session Started: ", EnumToString(session), " at ", TimeToString(startTime));
   // Your trading logic here
}
//
void OnOverlapStartHandler(ENUM_X_SESSION_OVERLAP_TYPE overlap, datetime startTime)
{
   Print("Overlap Started: ", EnumToString(overlap), " at ", TimeToString(startTime));
   // Your trading logic here (e.g., London-NY overlap = high liquidity)
}
//
void OnSessionStateChangeHandler(ENUM_X_SESSION_TYPE session, ENUM_X_SESSION_STATE state)
{
   Print("Session State Changed: ", EnumToString(session), " State: ", EnumToString(state));
}
//
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Initialize Alert
   gAlert = new XCAlert("SessionDetector");
   //
   // Initialize Session Detector
   gSessionDetector = new XCSessionDetector(
      gAlert,           // Alert Handler
      true,             // Use Server Time
      0                 // Timezone Offset (0 = Server Time)
   );
   //
   // Register Event Handlers
   gSessionDetector.AddOnSessionStartHandler(OnSessionStartHandler);
   gSessionDetector.AddOnOverlapStartHandler(OnOverlapStartHandler);
   gSessionDetector.AddOnSessionStateChangeHandler(OnSessionStateChangeHandler);
   //
   return(INIT_SUCCEEDED);
}
//
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Process Session Detection
   gSessionDetector.Process();
   //
   // You can also check active sessions manually
   ENUM_X_SESSION_TYPE activeSessions[];
   int activeCount = gSessionDetector.GetActiveSessions(activeSessions);
   //
   ENUM_X_SESSION_OVERLAP_TYPE activeOverlaps[];
   int overlapCount = gSessionDetector.GetActiveOverlaps(activeOverlaps);
   //
   // Trading logic based on sessions...
   if(overlapCount > 0 && activeOverlaps[0] == X_OVERLAP_LONDON_NEWYORK)
   {
      // London-NY Overlap = High Liquidity = Good for Trading
      // Your trading logic here
   }
}
//
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   delete gSessionDetector;
   delete gAlert;
}
//+------------------------------------------------------------------+

Key Features

  1. 7 Major Forex Sessions: Sydney, Tokyo, Singapore, Hong Kong, Frankfurt, London, New York
  2. 7 Session Overlaps: Including the critical London-New York overlap
  3. Event System: Register multiple event handlers for session start/end/overlap
  4. Timezone Support: Use server time or UTC with custom offset
  5. State Tracking: Track session states (Inactive, Active, Starting, Ending)
  6. Alert Integration: Built-in alert notifications for session changes
  7. Library Compatible: Follows SaherElm IT Center Library patterns

This class provides a global, unique, and extensible way to detect Forex sessions and trigger your trading logic based on session activity!