From 9d23e2cba1af25d2ed770392cb4e5de86c5e477b Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Fri, 14 Aug 2026 03:30:03 +0330 Subject: [PATCH] Last Works on XSDT Pivot Detector ... --- Classes/x-saherelm.pivot-detector.class.mq5 | 899 ++++++++++++++++++ Experts/x-saherelm.x-pivot-detector.test.mq5 | 414 ++++++++ Libraries/x-saherelm.x-poi.extensions.lib.mq5 | 68 ++ 3 files changed, 1381 insertions(+) create mode 100644 Classes/x-saherelm.pivot-detector.class.mq5 create mode 100644 Experts/x-saherelm.x-pivot-detector.test.mq5 diff --git a/Classes/x-saherelm.pivot-detector.class.mq5 b/Classes/x-saherelm.pivot-detector.class.mq5 new file mode 100644 index 00000000..6d199501 --- /dev/null +++ b/Classes/x-saherelm.pivot-detector.class.mq5 @@ -0,0 +1,899 @@ +/////////////////////////////////////////////////////// +// +// SaherElm IT Center MQL5 Class +// ------------------------------------------------- +// Name: XCPivotDetector +// Description: Forex Pivot 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 Pivot Detector Class" +#property strict + +// +// Imports ... +#include "../Classes/x-saherelm.base.class.mq5" +#include "../Classes/x-saherelm.x-alert.class.mq5" +#include "../Libraries/x-saherelm.common.extensions.lib.mq5" +#include "../Libraries/x-saherelm.x-enums.lib.mq5" +#include "../Libraries/x-saherelm.x-poi.extensions.lib.mq5" +#include "../Libraries/x-saherelm.x-poi.lib.mq5" + +// +// Definitions ... + +typedef void (*TOnPivotZoneEvent)(XBoxZone &box); + +// +// Implementation ... +class XCPivotDetector : public XCBase +{ + // + public: + // + // Props ... + // + XCAlert *alert; // Alert Handler + XBoxZone peaks[]; // Peaks ... + XBoxZone vales[]; // Vales ... + + // + // Event Handlers ... + TOnPivotZoneEvent mOnPeakBreakedHandlers[]; + TOnPivotZoneEvent mOnValeBreakedHandlers[]; + TOnPivotZoneEvent mOnPeakDetectedHandlers[]; + TOnPivotZoneEvent mOnValeDetectedHandlers[]; + + // + // Constructor ... + XCPivotDetector( + XCAlert *_alert = NULL, // + int _maxAllowedProcessableZones = 10 // + ) + { + // + alert = _alert; + + // + // Normalize ... + if (_maxAllowedProcessableZones <= 5) + { + _maxAllowedProcessableZones = 5; + } + mMaxAllowedProcessableZones = _maxAllowedProcessableZones; + + // + Initialize(); + } + + // + // Deconstructor ... + ~XCPivotDetector() + { + // + Clean(peaks); + Clean(vales); + + // + Clean(mOnPeakDetectedHandlers); + Clean(mOnValeDetectedHandlers); + + // + ZeroMemory(alert); + } + + // #region Actions ... + void Process( + XOHCL &bar, + int pivotApproved = 2, + int loopback = 20 // + ) + { + // + // Validate ... + if (!bar.IsValid()) + { + return; + } + + // + // Normalize ... + + // + // Privot Approve ... + if (pivotApproved < 1) + { + pivotApproved = 1; + } + + // + // Loopback ... + if (loopback < pivotApproved * 2) + { + loopback = pivotApproved * 2; + } + + // #region Peak/Vale Validation ... + UpdateZones(bar); + // #endregion + + // #region Peak/Vale Detection ... + // + // Loop through Loopback ... + XOHCL iBar; + bool has = false; + XBoxZone peakBox; + XBoxZone valeBox; + bool hasPeak = false; + bool hasVale = false; + int start = bar.Index(); + double jLow = EMPTY_VALUE; + double jHigh = EMPTY_VALUE; + int end = start + loopback; + double tmpPrice = EMPTY_VALUE; + double tmpZonePrice = EMPTY_VALUE; + for (int i = start; i < end; i++) + { + // + iBar.Clean(); + + // + // Ignore Loop if not pass Pivot Approve Length ... + if (i < start + pivotApproved) + { + continue; + } + + // + // Initialize Bar ... + has = iBar.Init( + bar.symbol, + bar.period, + i // + ); + if (!has) + { + continue; + } + + // + hasPeak = false; + hasVale = false; + + // + // Loop through Bars for Approved Side ... + int jStart = i + 1; + int jEnd = i + pivotApproved; + for (int j = jStart; j < jEnd; j++) + { + // + // Reading High and Low Prices ... + jLow = iLow(iBar.symbol, iBar.period, j); + jHigh = iHigh(iBar.symbol, iBar.period, j); + + // + hasVale = iBar.low < jLow; + hasPeak = iBar.high > jHigh; + has = hasVale || hasPeak; + if (!has) + { + break; + } + } + + // + // Check Peak or Vale Exists ... + has = hasPeak || hasVale; + if (!has) + { + // + iBar.Clean(); + continue; + } + + // + // Loop through Barse for Approved Side ... + jStart = i - pivotApproved; + jEnd = i - 1; + for (int j = jStart; j < jEnd; j++) + { + // + // Reading High and Low Prices ... + jLow = iLow(iBar.symbol, iBar.period, j); + jHigh = iHigh(iBar.symbol, iBar.period, j); + + // + if (hasPeak) + { + hasPeak = iBar.high > jHigh; + } + + // + if (hasVale) + { + hasVale = iBar.low < jLow; + } + + // + has = hasPeak || hasVale; + if (!has) + { + break; + } + } + + // + // Check Peak or Vale Exists ... + has = hasPeak || hasVale; + if (!has) + { + // + iBar.Clean(); + continue; + } + + // + // Handle Peaks ... + if (hasPeak) + { + // + // Detect Lower Price ... + tmpPrice = EMPTY_VALUE; + tmpZonePrice = EMPTY_VALUE; + jStart = i - pivotApproved; + jEnd = i + pivotApproved; + for (int j = jStart; j < jEnd; j++) + { + // + // Detect Max Price of Open/Close ... + tmpPrice = MathMax( + iClose(iBar.symbol, iBar.period, j), + iOpen(iBar.symbol, iBar.period, j) // + ); + + // + tmpZonePrice = + NotEmpty(tmpZonePrice) + ? tmpPrice > tmpZonePrice + ? tmpPrice + : tmpZonePrice + : tmpPrice; + } + + // + // Initialize Peak Box ... + peakBox.Clean(); + has = peakBox.Init( + iBar.symbol, + iBar.period, + iBar.high, + tmpZonePrice, + iBar.time, + bar.time, + X_DIRECTION_BEARISH, + mPivotPrefix // + ); + + // + HandlePeakDetected(peakBox); + } + + // + // Handle Vales ... + if (hasVale) + { + // + // Detect Higher Price ... + tmpPrice = EMPTY_VALUE; + tmpZonePrice = EMPTY_VALUE; + jStart = i - pivotApproved; + jEnd = i + pivotApproved; + for (int j = jStart; j < jEnd; j++) + { + // + // Detect Max Price of Open/Close ... + tmpPrice = MathMin( + iClose(iBar.symbol, iBar.period, j), + iOpen(iBar.symbol, iBar.period, j) // + ); + + // + tmpZonePrice = + NotEmpty(tmpZonePrice) + ? tmpPrice < tmpZonePrice + ? tmpPrice + : tmpZonePrice + : tmpPrice; + } + + // + // Initialize Vale Box ... + valeBox.Clean(); + has = valeBox.Init( + iBar.symbol, + iBar.period, + tmpZonePrice, + iBar.low, + iBar.time, + bar.time, + X_DIRECTION_BULLISH, + mPivotPrefix // + ); + + // + HandleValeDetected(valeBox); + } + + // + iBar.Clean(); + peakBox.Clean(); + valeBox.Clean(); + } + // #endregion + } + // #endregion + + // #region Event Listener Registration ... + /** + * Add Peak Breaked Event Handler + * + * @param handler: TOnPivotZoneEvent + */ + void AddOnPeakBreakedHandler(TOnPivotZoneEvent handler) + { + // + if (handler == NULL) + { + return; + } + + // + Add(handler, mOnPeakBreakedHandlers); + } + + /** + * Add Vale Breaked Event Handler + * + * @param handler: TOnPivotZoneEvent + */ + void AddOnValeBreakedHandler(TOnPivotZoneEvent handler) + { + // + if (handler == NULL) + { + return; + } + + // + Add(handler, mOnValeBreakedHandlers); + } + + /** + * Add Peak Detected Event Handler + * + * @param handler: TOnPivotZoneEvent + */ + void AddOnPeakDetectedHandler(TOnPivotZoneEvent handler) + { + // + if (handler == NULL) + { + return; + } + + // + Add(handler, mOnPeakDetectedHandlers); + } + + /** + * Add Vale Detected Event Handler + * + * @param handler: TOnPivotZoneEvent + */ + void AddOnValeDetectedHandler(TOnPivotZoneEvent handler) + { + // + if (handler == NULL) + { + return; + } + + // + Add(handler, mOnValeDetectedHandlers); + } + // #endregion + + // + protected: + /** + * Initialize Class ... + */ + void Initialize() + { + // + Clean(peaks); + Clean(vales); + + // + Clean(mOnPeakDetectedHandlers); + Clean(mOnValeDetectedHandlers); + + // + mPivotPrefix = "XSDT"; + } + + /** + * Handle Peak Box Detection ... + * + * @param peakBox: XBoxZone instance ... + */ + void HandlePeakDetected( + XBoxZone &peakBox // + ) + { + // + // Validate ... + if (!peakBox.IsValid()) + { + return; + } + + // + // Update To ... + peakBox.to = TimeCurrent(); + + // + // Check Peak Exists ... + bool riseEvent = false; + bool hasPeaks = HasChild(peaks); + if (!hasPeaks) + { + // + // Add to Peaks ... + AddRef(peakBox, peaks); + riseEvent = true; + } + else + { + // + // Validate ... + int idx = -1; + hasPeaks = FindIndex( + idx, + peakBox, + peaks // + ); + + // + // Only Valid if there is not any Peak Registered ... + if (!hasPeaks) + { + // + // Detect Nearest Peak ... + idx = GetYoungest(peaks); + hasPeaks = IsValidIndex(idx); + if (hasPeaks) + { + // + // Validate New Peak based on Youngest One ... + hasPeaks = peakBox.upper > peaks[idx].upper; + if (hasPeaks) + { + // + AddRef(peakBox, peaks); + riseEvent = true; + } + } + } + } + + // + // Handle Event ... + if (riseEvent) + { + NotifyPeakDetected(peakBox); + } + + // + CleanupPeaks(); + } + + /** + * Handle Vale Box Detection ... + * + * @param valeBox: XBoxZone instance ... + */ + void HandleValeDetected( + XBoxZone &valeBox // + ) + { + // + // Validate ... + if (!valeBox.IsValid()) + { + return; + } + + // + // Update To ... + valeBox.to = TimeCurrent(); + + // + bool riseEvent = false; + bool hasVales = HasChild(vales); + if (!hasVales) + { + // + // Add to Vales ... + AddRef(valeBox, vales); + riseEvent = true; + } + else + { + // + // Validate ... + int idx = -1; + hasVales = FindIndex( + idx, + valeBox, + vales // + ); + + // + // Only Valid if there is not any Vale Registered ... + if (!hasVales) + { + // + // Detect Nearest Vale ... + idx = GetYoungest(vales); + hasVales = IsValidIndex(idx); + if (hasVales) + { + // + // Validate New Vale based on Youngest One ... + hasVales = valeBox.lower < vales[idx].lower; + if (hasVales) + { + // + AddRef(valeBox, vales); + riseEvent = true; + } + } + } + } + + // + // Handle Event ... + if (riseEvent) + { + NotifyValeDetected(valeBox); + } + + // + CleanupVales(); + } + + // #region Event Notifiers ... + /** + * Notfiy New Peak Breaked ... + * + * @param box: XBoxZone instance ... + */ + void NotifyPeakBreaked(XBoxZone &box) + { + // + // Validate ... + if (!box.IsValid()) + { + return; + } + + // + // Handle Log ... + if (alert != NULL) + { + // + // Preparing Log Message ... + string message = "Peak Breaked: " + DoubleToString(box.upper, 4) + + " at " + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); + + // + // Sending Alert ... + alert.SendAlert(message); + } + + // + // Multiple Handlers ... + int count = ArraySize(mOnPeakBreakedHandlers); + for (int i = 0; i < count; i++) + { + mOnPeakBreakedHandlers[i](box); + } + } + + /** + * Notfiy New Vale Breaked ... + * + * @param box: XBoxZone instance ... + */ + void NotifyValeBreaked(XBoxZone &box) + { + // + // Validate ... + if (!box.IsValid()) + { + return; + } + + // + // Handle Log ... + if (alert != NULL) + { + // + // Preparing Log Message ... + string message = "Vale Breaked: " + DoubleToString(box.lower, 4) + + " at " + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); + + // + // Sending Alert ... + alert.SendAlert(message); + } + + // + // Multiple Handlers ... + int count = ArraySize(mOnValeBreakedHandlers); + for (int i = 0; i < count; i++) + { + mOnValeBreakedHandlers[i](box); + } + } + + /** + * Notfiy New Peak Detected ... + * + * @param box: XBoxZone instance ... + */ + void NotifyPeakDetected(XBoxZone &box) + { + // + // Validate ... + if (!box.IsValid()) + { + return; + } + + // + // Handle Log ... + if (alert != NULL) + { + // + // Preparing Log Message ... + string message = "Peak Detected: " + DoubleToString(box.upper, 4) + + " at " + TimeToString(box.from, TIME_DATE | TIME_SECONDS); + + // + // Sending Alert ... + alert.SendAlert(message); + } + + // + // Multiple Handlers ... + int count = ArraySize(mOnPeakDetectedHandlers); + for (int i = 0; i < count; i++) + { + mOnPeakDetectedHandlers[i](box); + } + } + + /** + * Notfiy New Vale Detected ... + * + * @param box: XBoxZone instance ... + */ + void NotifyValeDetected(XBoxZone &box) + { + // + // Validate ... + if (!box.IsValid()) + { + return; + } + + // + // Handle Log ... + if (alert != NULL) + { + // + // Preparing Log Message ... + string message = "Vale Detected: " + DoubleToString(box.lower, 4) + + " at " + TimeToString(box.from, TIME_DATE | TIME_SECONDS); + + // + // Sending Alert ... + alert.SendAlert(message); + } + + // + // Multiple Handlers ... + int count = ArraySize(mOnValeDetectedHandlers); + for (int i = 0; i < count; i++) + { + mOnValeDetectedHandlers[i](box); + } + } + // #endregion + + // + private: + // + // Props ... + string mPivotPrefix; // Prefix ... + int mMaxAllowedProcessableZones; // Max Allowed Processable Zones ... + + /** + * Handle Apply Max Allowed Processable Peaks ... + */ + void CleanupPeaks() + { + // + if (ArraySize(peaks) <= mMaxAllowedProcessableZones) + { + return; + } + + // + Sort( + peaks, // Collection to Sort ... + X_SORT_BY_DATE, // Sorted By ... + X_DIRECTION_BEARISH, // Direction of Sorting ... + mMaxAllowedProcessableZones // Max Allowed ... + ); + } + + /** + * Handle Apply Max Allowed Processable Vales ... + */ + void CleanupVales() + { + // + if (ArraySize(vales) <= mMaxAllowedProcessableZones) + { + return; + } + + // + Sort( + vales, // Collection to Sort ... + X_SORT_BY_DATE, // Sorted By ... + X_DIRECTION_BEARISH, // Direction of Sorting ... + mMaxAllowedProcessableZones // Max Allowed ... + ); + } + + /** + * if there is Zones in Peaks or Vales + * Update their To Times ... + * + * @param bar: XOHCL instance ... + **/ + void UpdateZones(XOHCL &bar) + { + // + // Check Bar is Valid ... + if (bar.IsValid()) + { + // + // Retrieve Last Closed Bar if not Provided ... + XOHCL cBar; + if (bar.Index() == 1) + { + cBar = bar; + } + else + { + cBar.Init(bar.symbol, bar.period, 1); + } + + // + // Validate CBar ... + // Validate Peaks and Vales ... + if (cBar.IsValid()) + { + // + int removeIDX[]; + + // + if (HasChild(peaks)) + { + // + Clean(removeIDX); + for (int i = 0; i < ArraySize(peaks); i++) + { + // + // Validate Zone by Close Price ... + if (cBar.close > peaks[i].upper) + { + // + Add(i, removeIDX); + NotifyPeakBreaked(peaks[i]); + } + } + + // + if (HasChild(removeIDX)) + { + CleanupArray(removeIDX, peaks); + } + } + + // + if (HasChild(vales)) + { + // + Clean(removeIDX); + for (int i = 0; i < ArraySize(vales); i++) + { + // + // Validate Zone by Close Price ... + if (cBar.close < vales[i].lower) + { + // + Add(i, removeIDX); + NotifyValeBreaked(vales[i]); + } + } + + // + if (HasChild(removeIDX)) + { + CleanupArray(removeIDX, vales); + } + } + } + + // + cBar.Clean(); + } + + // + datetime tc = TimeCurrent(); + + // + bool has = HasChild(peaks); + if (has) + { + // + for (int i = 0; i < ArraySize(peaks); i++) + { + peaks[i].to = tc; + } + } + + // + has = HasChild(vales); + if (has) + { + // + for (int i = 0; i < ArraySize(vales); i++) + { + vales[i].to = tc; + } + } + + // + CleanupPeaks(); + CleanupVales(); + } +}; + +// \ No newline at end of file diff --git a/Experts/x-saherelm.x-pivot-detector.test.mq5 b/Experts/x-saherelm.x-pivot-detector.test.mq5 new file mode 100644 index 00000000..939f8023 --- /dev/null +++ b/Experts/x-saherelm.x-pivot-detector.test.mq5 @@ -0,0 +1,414 @@ +/////////////////////////////////////////////////////// +// +// SaherElm IT Center MQL5 Expert Advisor +// ------------------------------------------------- +// Name: XBaseEA +// Description: Session Detector Test Expert Advisor ... +// +// Maintainer: +// ------------ +// Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) +// +////////////////////////////////////////////////////// +// +// Global Properties ... +#property copyright "Copyright 2023, SaherElm IT Center" +#property link "https://saherelm.ir" +#property version "1.00" +#property icon "../Images/SaherElm_Logo_Color.ico" +#property description "SaherElm Session Detector Test Expert Advisor" +#property strict + +// #region Imports ... +#include "../Classes/x-saherelm.pivot-detector.class.mq5" +#include "../Classes/x-saherelm.session-detector.class.mq5" +#include "../Classes/x-saherelm.x-poi.drawer.class.mq5" +// #endregion + +// #region Global Variables ... +#define ShortName "XPivotDetectorTestEA" + +XCAlert *eaAlert; +XCPOIDrawer *eaDrawer; +CArrayObj eaDrawnObjects; +XCPivotDetector *eaPivotDetector; +XCSessionDetector *eaSessionDetector; + +// +// Last Processing Pivots Time ... +datetime eaLastPivotProcessing = NULL; +// #endregion + +// #region Global Event Handlers ... +/** + * Initialize EA Process ... + * + * @return ( int ) + */ +int OnInit() +{ + // + bool result = true; + + // + result = InitializeRequirements(); + if (!result) + { + return INIT_FAILED; + } + + // + return INIT_SUCCEEDED; +} + +/** + * De Initialize Expert ... + * + * @param reason: int, DeInitialization Reason ... + * + * REASON_PROGRAM 0 The EA has stopped working calling the ExpertRemove() function + * REASON_REMOVE 1 Program removed from a chart + * REASON_RECOMPILE 2 Program recompiled + * REASON_CHARTCHANGE 3 A symbol or a chart period is changed + * REASON_CHARTCLOSE 4 Chart closed + * REASON_PARAMETERS 5 Inputs changed by a user + * REASON_ACCOUNT 6 Another account has been activated or reconnection to the trade server has occurred due to changes in the account settings + * REASON_TEMPLATE 7 Another chart template applied + * REASON_INITFAILED 8 The OnInit() handler returned a non-zero value + * REASON_CLOSE 9 Terminal closed + * + */ +void OnDeinit(const int reason) +{ + // + // Destroy Timer ... + // If Initialized ... + + // + // Cleanup Resources ... + ZeroMemory(eaAlert); + ZeroMemory(eaDrawer); + ZeroMemory(eaPivotDetector); + ZeroMemory(eaSessionDetector); +} + +/** + * Handle Tick for Processing ... + */ +void OnTick() +{ + // + // Perform Session Detector Process ... + eaSessionDetector.Process(); + + // + // Process Pivot Detector ... + HandleProcessPviots(); +} + +/** + * Handle On Chart Event Processing ... + + * @param id: int, Specified Event ID ... + * @param lparam: long, type event parameter + * @param dparam: double, type event parameter + * @param sparam: string, type event parameter + */ +void OnChartEvent( + const int id, + const long &lparam, + const double &dparam, + const string &sparam // +) +{ +} +// #endregion + +// #region Custom Actions ... +/** + * Initial Requirements of EA ... + * + * @return ( bool ) + */ +bool InitializeRequirements() +{ + // + bool result = false; + + // + eaDrawer = new XCPOIDrawer(); + + // + // Initialize Alert ... + eaAlert = new XCAlert( + ShortName, + true, // Enable Alert ... + true, // Log Alert ... + false, // Terminal Alert ... + false, // Mail Alert ... + false, // Push Alerts ... + false // Save Alerts ... + ); + result = eaAlert != NULL; + if (!result) + { + return result; + } + + // + // Initialize Session Detector ... + eaSessionDetector = new XCSessionDetector( + eaAlert, // Alert ... + true, // Use Server Time ... + 0 // Timezone Offset ... + ); + + // + // Register Event Handlers ... + eaSessionDetector.AddOnSessionEndHandler(OnSessionEndHandler); + eaSessionDetector.AddOnOverlapEndHandler(OnOverlapEndHandler); + eaSessionDetector.AddOnSessionStartHandler(OnSessionStartHandler); + eaSessionDetector.AddOnOverlapStartHandler(OnOverlapStartHandler); + eaSessionDetector.AddOnSessionStateChangeHandler(OnSessionStateChangeHandler); + + // + result = eaSessionDetector != NULL; + if (!result) + { + return result; + } + + // + // Initialize Pivot Detector ... + eaPivotDetector = new XCPivotDetector( + eaAlert // Alert ... + ); + + // + // Register Event Handlers ... + eaPivotDetector.AddOnPeakBreakedHandler(OnPeakBreakedHandler); + eaPivotDetector.AddOnValeBreakedHandler(OnValeBreakedHandler); + eaPivotDetector.AddOnPeakDetectedHandler(OnPeakDetectedHandler); + eaPivotDetector.AddOnValeDetectedHandler(OnValeDetectedHandler); + + // + result = eaPivotDetector != NULL; + if (!result) + { + return result; + } + + // + return result; +} + +/** + * Process Pivots ... + */ +void HandleProcessPviots() +{ + // + // Initialize Pivot Processing Bar ... + XOHCL bar; + bool has = bar.Init( + _Symbol, + PERIOD_H4, + 1 // Last Completed 4H Bar ... + ); + if (!has) + { + return; + } + + // + // Validate Passing Bar ... + has = + !IsXValid(eaLastPivotProcessing) || + bar.time > eaLastPivotProcessing; + if (!has) + { + return; + } + + // + eaPivotDetector.Process( + bar, // XOHCL Bar ... + 2, // Pivot Approvation Length ... + 20 // Loopback ... + ); + + // + // Handle Draw Pivots ... + XBoxZone tmps[]; + CArrayObj *objects; + FillNotExists( + tmps, // Dest ... + eaPivotDetector.peaks, // Source ... + false // Force Clean ... + ); + FillNotExists( + tmps, // Dest ... + eaPivotDetector.vales, // Source ... + false // Force Clean ... + ); + if (HasChild(tmps)) + { + eaDrawer.DrawBoxes( + tmps, // Boxes ... + objects, // Objects to Hold ... + false // Ignore Type Name ... + ); + } + XClean(tmps); + AddObjects(objects, eaDrawnObjects); + if (eaDrawnObjects.Total() > 0) { + DebugBreak(); + } +} +// #endregion + +// #region Custom Event Handlers ... + +// #region Pivot Detector Event Handlers ... +/** + * Calls when a Peak Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnPeakBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a Vale Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnValeBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a New Peak Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnPeakDetectedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a New Vale Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnValeDetectedHandler(XBoxZone &box) +{ + // DebugBreak(); +} +// #endregion + +// #region Session Detector Event Handlers ... +/** + * Handle Session Start Event ... + * + * @param session: ENUM_X_SESSION_TYPE + * @param startTime: datetime + */ +void OnSessionStartHandler( + ENUM_X_SESSION_TYPE session, + datetime startTime // +) +{ + // Print("Session Started: ", EnumToString(session), " at ", TimeToString(startTime)); + + // // + // // Custom Testing ... + // if (session == X_SESSION_NEWYORK) + // { + // DebugBreak(); + // } +} + +/** + * Handle Session End Event ... + * + * @param session: ENUM_X_SESSION_TYPE + * @param startTime: datetime + */ +void OnSessionEndHandler( + ENUM_X_SESSION_TYPE session, + datetime startTime // +) +{ + // Print("Session Ended: ", EnumToString(session), " at ", TimeToString(startTime)); + + // // + // // Custom Testing ... + // if (session == X_SESSION_NEWYORK) + // { + // DebugBreak(); + // } +} + +/** + * Handle Overlap Start Event ... + * + * @param session: ENUM_X_SESSION_OVERLAP_TYPE + * @param startTime: datetime + */ +void OnOverlapStartHandler( + ENUM_X_SESSION_OVERLAP_TYPE overlap, + datetime startTime // +) +{ + // Print("Overlap Started: ", EnumToString(overlap), " at ", TimeToString(startTime)); + + // // + // // Custom Testing ... + // if (overlap == X_OVERLAP_LONDON_NEWYORK) + // { + // DebugBreak(); + // } +} + +/** + * Handle Overlap End Event ... + * + * @param session: ENUM_X_SESSION_OVERLAP_TYPE + * @param startTime: datetime + */ +void OnOverlapEndHandler( + ENUM_X_SESSION_OVERLAP_TYPE overlap, + datetime startTime // +) +{ + // Print("Overlap Ended: ", EnumToString(overlap), " at ", TimeToString(startTime)); + + // // + // // Custom Testing ... + // if (overlap == X_OVERLAP_LONDON_NEWYORK) + // { + // DebugBreak(); + // } +} + +/** + * Handle Session State Change Event ... + * + * @param session: ENUM_X_SESSION_TYPE + * @param state: ENUM_X_SESSION_STATE + */ +void OnSessionStateChangeHandler( + ENUM_X_SESSION_TYPE session, + ENUM_X_SESSION_STATE state // +) +{ + // Print("Session State Changed: ", EnumToString(session), " State: ", EnumToString(state)); +} +// #endregion + +// #endregion \ No newline at end of file diff --git a/Libraries/x-saherelm.x-poi.extensions.lib.mq5 b/Libraries/x-saherelm.x-poi.extensions.lib.mq5 index 22444b8c..93739be6 100644 --- a/Libraries/x-saherelm.x-poi.extensions.lib.mq5 +++ b/Libraries/x-saherelm.x-poi.extensions.lib.mq5 @@ -3482,6 +3482,74 @@ int FillNotExists( // return result; } + +template +int FillNotExists( + T &dest[], + T &source[], + bool forceClean = false, + ENUM_X_DIRECTION forDir = X_DIRECTION_NONE, + int maxAllowed = 0 // +) +{ + // + int result = 0; + + // + // Prepare ... + if (forceClean) + { + XClean(dest); + } + + // + // Normalize ... + maxAllowed = NormalizeInt(maxAllowed, 0); + + // + // Validate ... + bool has = HasChild(source); + if (!has) + { + return result; + } + + // + int count = ArraySize(source); + for (int i = 0; i < count; i++) + { + // + has = + source[i].IsValid() && + (!HasDirection(forDir) || forDir == source[i].dir); + if (!has) + { + continue; + } + + // + has = AddIfNotExists(source[i], dest); + if (has) + { + result++; + } + } + + // + // Handle Cleanup Collection + // if Max Reached ... + if (maxAllowed > 0) + { + // + CleanupArray( + dest, + maxAllowed // + ); + } + + // + return result; +} //+------------------------------------------------------------------+ //+------------------------------------------------------------------+