diff --git a/Classes/x-saherelm.pivot-detector.class.mq5 b/Classes/x-saherelm.pivot-detector.class.mq5 index 6d199501..3a93c275 100644 --- a/Classes/x-saherelm.pivot-detector.class.mq5 +++ b/Classes/x-saherelm.pivot-detector.class.mq5 @@ -57,8 +57,10 @@ class XCPivotDetector : public XCBase // // Constructor ... XCPivotDetector( - XCAlert *_alert = NULL, // - int _maxAllowedProcessableZones = 10 // + XCAlert *_alert = NULL, // Alert instance Pointer ... + string _pivotPrefix = "XPivot", // Detected Zones Prefix ... + bool _supportInvertedZones = false, // Specified Support Inverted Zone or not ... + int _maxAllowedProcessableZones = 0 // Max Allowed Processable Zones ... ) { // @@ -66,10 +68,12 @@ class XCPivotDetector : public XCBase // // Normalize ... - if (_maxAllowedProcessableZones <= 5) + if (_maxAllowedProcessableZones < 0) { - _maxAllowedProcessableZones = 5; + _maxAllowedProcessableZones = 0; } + mPivotPrefix = _pivotPrefix; + mSupportInvertedZones = _supportInvertedZones; mMaxAllowedProcessableZones = _maxAllowedProcessableZones; // @@ -167,13 +171,13 @@ class XCPivotDetector : public XCBase } // - hasPeak = false; - hasVale = false; + hasPeak = true; + hasVale = true; // // Loop through Bars for Approved Side ... int jStart = i + 1; - int jEnd = i + pivotApproved; + int jEnd = i + pivotApproved + 1; for (int j = jStart; j < jEnd; j++) { // @@ -182,8 +186,8 @@ class XCPivotDetector : public XCBase jHigh = iHigh(iBar.symbol, iBar.period, j); // - hasVale = iBar.low < jLow; - hasPeak = iBar.high > jHigh; + hasVale = hasVale && (iBar.low < jLow); + hasPeak = hasPeak && (iBar.high > jHigh); has = hasVale || hasPeak; if (!has) { @@ -204,7 +208,7 @@ class XCPivotDetector : public XCBase // // Loop through Barse for Approved Side ... jStart = i - pivotApproved; - jEnd = i - 1; + jEnd = i; for (int j = jStart; j < jEnd; j++) { // @@ -251,7 +255,7 @@ class XCPivotDetector : public XCBase tmpPrice = EMPTY_VALUE; tmpZonePrice = EMPTY_VALUE; jStart = i - pivotApproved; - jEnd = i + pivotApproved; + jEnd = i + pivotApproved + 1; for (int j = jStart; j < jEnd; j++) { // @@ -297,7 +301,7 @@ class XCPivotDetector : public XCBase tmpPrice = EMPTY_VALUE; tmpZonePrice = EMPTY_VALUE; jStart = i - pivotApproved; - jEnd = i + pivotApproved; + jEnd = i + pivotApproved + 1; for (int j = jStart; j < jEnd; j++) { // @@ -341,6 +345,46 @@ class XCPivotDetector : public XCBase } // #endregion } + + bool HasPeaks() + { + return HasChild(peaks); + } + + bool HasVales() + { + return HasChild(vales); + } + + int GetYoungestPeak() + { + // + int result = -1; + + // + if (HasPeaks()) + { + result = GetYoungest(peaks); + } + + // + return result; + } + + int GetYoungestVale() + { + // + int result = -1; + + // + if (HasVales()) + { + result = GetYoungest(vales); + } + + // + return result; + } // #endregion // #region Event Listener Registration ... @@ -427,9 +471,6 @@ class XCPivotDetector : public XCBase // Clean(mOnPeakDetectedHandlers); Clean(mOnValeDetectedHandlers); - - // - mPivotPrefix = "XSDT"; } /** @@ -448,6 +489,13 @@ class XCPivotDetector : public XCBase return; } + // + // Validate Peak Box based on Price ... + if (!IsBoxValid(peakBox, 0, X_BOUNDARY_PRICE_UP_DOWN)) + { + return; + } + // // Update To ... peakBox.to = TimeCurrent(); @@ -486,7 +534,7 @@ class XCPivotDetector : public XCBase { // // Validate New Peak based on Youngest One ... - hasPeaks = peakBox.upper > peaks[idx].upper; + hasPeaks = peakBox.upper < peaks[idx].upper; if (hasPeaks) { // @@ -524,6 +572,13 @@ class XCPivotDetector : public XCBase return; } + // + // Validate Peak Box based on Price ... + if (!IsBoxValid(valeBox, 0, X_BOUNDARY_PRICE_UP_DOWN)) + { + return; + } + // // Update To ... valeBox.to = TimeCurrent(); @@ -561,7 +616,7 @@ class XCPivotDetector : public XCBase { // // Validate New Vale based on Youngest One ... - hasVales = valeBox.lower < vales[idx].lower; + hasVales = valeBox.lower > vales[idx].lower; if (hasVales) { // @@ -738,6 +793,7 @@ class XCPivotDetector : public XCBase // // Props ... string mPivotPrefix; // Prefix ... + bool mSupportInvertedZones; // Support Inverted Zones ... int mMaxAllowedProcessableZones; // Max Allowed Processable Zones ... /** @@ -826,6 +882,7 @@ class XCPivotDetector : public XCBase // Add(i, removeIDX); NotifyPeakBreaked(peaks[i]); + HandleBreakedZone(peaks[i], cBar); } } @@ -850,6 +907,7 @@ class XCPivotDetector : public XCBase // Add(i, removeIDX); NotifyValeBreaked(vales[i]); + HandleBreakedZone(vales[i], cBar); } } @@ -894,6 +952,87 @@ class XCPivotDetector : public XCBase CleanupPeaks(); CleanupVales(); } + + /** + * Handle Breaked Zones ... + * + * @param box: XBoxZone instance ... + * @param bar: XOHCL instance ... + */ + void HandleBreakedZone( + XBoxZone &box, + XOHCL &bar // + ) + { + // + // Validate ... + if (!box.IsValid() || + !mSupportInvertedZones // + ) + { + return; + } + + // + XBoxZone inverted; + int maxAllowedInversion = 1; + int count = ArraySize(box.breaks); + bool has = count < maxAllowedInversion; + if (!has) + { + return; + } + + // + // Invert to Peak ... + if (box.IsBullish()) + { + // + // Initialize Inverted Box ... + has = inverted.Init( + box.symbol, + box.period, + box.upper, + box.lower, + bar.time, // From ... + TimeCurrent(), // To ... + X_DIRECTION_BEARISH, + "i" + mPivotPrefix // Type ... + ); + if (has) + { + // + Add(bar.time, inverted.breaks); + AddRef(inverted, peaks); + } + } + // + // Invert to Vale ... + else if (box.IsBearish()) + { + // + // Initialize Inverted Box ... + has = inverted.Init( + box.symbol, + box.period, + box.upper, + box.lower, + bar.time, // From ... + TimeCurrent(), // To ... + X_DIRECTION_BULLISH, + "i" + mPivotPrefix // Type ... + ); + if (has) + { + // + Add(bar.time, inverted.breaks); + AddRef(inverted, vales); + } + } + + // + inverted.Clean(); + } }; // \ No newline at end of file diff --git a/Documents/GeneratedContext/x121XSDT-20260814_052256.txt b/Documents/GeneratedContext/x121XSDT-20260814_052256.txt new file mode 100644 index 00000000..761e4df1 --- /dev/null +++ b/Documents/GeneratedContext/x121XSDT-20260814_052256.txt @@ -0,0 +1,1319 @@ + +### FILE: C:\Users\SaherElm\AppData\Roaming\MetaQuotes\Terminal\572C4A1F743028C65C8BA0B780030F0D\MQL5\Classes\x-saherelm.pivot-detector.class.mq5 + +/////////////////////////////////////////////////////// +// +// 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 = true; + hasVale = true; + + // + // Loop through Bars for Approved Side ... + int jStart = i + 1; + int jEnd = i + pivotApproved + 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); + + // + hasVale = hasVale && (iBar.low < jLow); + hasPeak = 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; + 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 + 1; + 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 + 1; + 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(); + } +}; + +// + +### FILE: C:\Users\SaherElm\AppData\Roaming\MetaQuotes\Terminal\572C4A1F743028C65C8BA0B780030F0D\MQL5\Experts\x-saherelm.x-pivot-detector.test.mq5 + +/////////////////////////////////////////////////////// +// +// 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; + } + + // + eaLastPivotProcessing = bar.time; + + // + 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); +} +// #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 diff --git a/Documents/GeneratedContext/x121XSDT-20260814_080956.txt b/Documents/GeneratedContext/x121XSDT-20260814_080956.txt new file mode 100644 index 00000000..066cdb66 --- /dev/null +++ b/Documents/GeneratedContext/x121XSDT-20260814_080956.txt @@ -0,0 +1,1635 @@ + +### FILE: C:\Users\SaherElm\AppData\Roaming\MetaQuotes\Terminal\572C4A1F743028C65C8BA0B780030F0D\MQL5\Classes\x-saherelm.pivot-detector.class.mq5 + +/////////////////////////////////////////////////////// +// +// 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, // Alert instance Pointer ... + string _pivotPrefix = "XPivot", // Detected Zones Prefix ... + bool _supportInvertedZones = false, // Specified Support Inverted Zone or not ... + int _maxAllowedProcessableZones = 0 // Max Allowed Processable Zones ... + ) + { + // + alert = _alert; + + // + // Normalize ... + if (_maxAllowedProcessableZones < 0) + { + _maxAllowedProcessableZones = 0; + } + mPivotPrefix = _pivotPrefix; + mSupportInvertedZones = _supportInvertedZones; + 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 = true; + hasVale = true; + + // + // Loop through Bars for Approved Side ... + int jStart = i + 1; + int jEnd = i + pivotApproved + 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); + + // + hasVale = hasVale && (iBar.low < jLow); + hasPeak = 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; + 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 + 1; + 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 + 1; + 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); + } + + /** + * Handle Peak Box Detection ... + * + * @param peakBox: XBoxZone instance ... + */ + void HandlePeakDetected( + XBoxZone &peakBox // + ) + { + // + // Validate ... + if (!peakBox.IsValid()) + { + return; + } + + // + // Validate Peak Box based on Price ... + if (!IsBoxValid(peakBox, 0, X_BOUNDARY_PRICE_UP_DOWN)) + { + 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; + } + + // + // Validate Peak Box based on Price ... + if (!IsBoxValid(valeBox, 0, X_BOUNDARY_PRICE_UP_DOWN)) + { + 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 ... + bool mSupportInvertedZones; // Support Inverted Zones ... + 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]); + HandleBreakedZone(peaks[i], cBar); + } + } + + // + 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]); + HandleBreakedZone(vales[i], cBar); + } + } + + // + 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(); + } + + /** + * Handle Breaked Zones ... + * + * @param box: XBoxZone instance ... + * @param bar: XOHCL instance ... + */ + void HandleBreakedZone( + XBoxZone &box, + XOHCL &bar // + ) + { + // + // Validate ... + if (!box.IsValid() || + !mSupportInvertedZones // + ) + { + return; + } + + // + XBoxZone inverted; + int maxAllowedInversion = 1; + int count = ArraySize(box.breaks); + bool has = count < maxAllowedInversion; + if (!has) + { + return; + } + + // + // Invert to Peak ... + if (box.IsBullish()) + { + // + // Initialize Inverted Box ... + has = inverted.Init( + box.symbol, + box.period, + box.upper, + box.lower, + bar.time, // From ... + TimeCurrent(), // To ... + X_DIRECTION_BEARISH, + "i" + mPivotPrefix // Type ... + ); + if (has) + { + // + Add(bar.time, inverted.breaks); + AddRef(inverted, peaks); + } + } + // + // Invert to Vale ... + else if (box.IsBearish()) + { + // + // Initialize Inverted Box ... + has = inverted.Init( + box.symbol, + box.period, + box.upper, + box.lower, + bar.time, // From ... + TimeCurrent(), // To ... + X_DIRECTION_BULLISH, + "i" + mPivotPrefix // Type ... + ); + if (has) + { + // + Add(bar.time, inverted.breaks); + AddRef(inverted, vales); + } + } + + // + inverted.Clean(); + } +}; + +// + +### FILE: C:\Users\SaherElm\AppData\Roaming\MetaQuotes\Terminal\572C4A1F743028C65C8BA0B780030F0D\MQL5\Experts\x-saherelm.x-pivot-detector.test.mq5 + +/////////////////////////////////////////////////////// +// +// 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; +XCSessionDetector *eaSessionDetector; + +// +XCPivotDetector *eaMajorPivotDetector; +XCPivotDetector *eaMinorPivotDetector; + +// +// Last Processing Pivots Time ... +bool eaDrawMajorPivots = true; +bool eaDrawMinorPivots = true; +XPOIDrawConfig eaMajorPivotsDrawConfig; +XPOIDrawConfig eaMinorPivotsDrawConfig; +XPOIDrawConfig eaPointedMajorPivotsDrawConfig; +XPOIDrawConfig eaPointedMinorPivotsDrawConfig; +datetime eaLastMajorPivotProcessing = NULL; +datetime eaLastMinorPivotProcessing = 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 ... + + // + eaMajorPivotsDrawConfig.Clean(); + eaMinorPivotsDrawConfig.Clean(); + eaPointedMajorPivotsDrawConfig.Clean(); + eaPointedMinorPivotsDrawConfig.Clean(); + + // + // Cleanup Resources ... + ZeroMemory(eaAlert); + ZeroMemory(eaDrawer); + ZeroMemory(eaSessionDetector); + ZeroMemory(eaMajorPivotDetector); + ZeroMemory(eaMinorPivotDetector); +} + +/** + * Handle Tick for Processing ... + */ +void OnTick() +{ + // + // Perform Session Detector Process ... + eaSessionDetector.Process(); + + // + // Process Pivot Detector ... + HandleProcessMajorPviots(); + HandleProcessMinorPviots(); +} + +/** + * 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, + false, // Enable Alert ... + false, // 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 Major Pivot Detector ... + eaMajorPivotDetector = new XCPivotDetector( + eaAlert, // Alert ... + "X121SDTMajor", // Pivot Prefix ... + false, // Support Inverted Zones ... + 0 // Processable Zones ... + ); + + // + // Register Event Handlers ... + eaMajorPivotDetector.AddOnPeakBreakedHandler(OnMajorPeakBreakedHandler); + eaMajorPivotDetector.AddOnValeBreakedHandler(OnMajorValeBreakedHandler); + eaMajorPivotDetector.AddOnPeakDetectedHandler(OnMajorPeakDetectedHandler); + eaMajorPivotDetector.AddOnValeDetectedHandler(OnMajorValeDetectedHandler); + + // + // Pivot Draw Config ... + eaMajorPivotsDrawConfig.zonesWidth = 2; + eaMajorPivotsDrawConfig.zonesFill = false; + eaMajorPivotsDrawConfig.ignoreZonesAt = true; + eaMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID; + eaMajorPivotsDrawConfig.bullishColor = clrAqua; + eaMajorPivotsDrawConfig.bearishColor = clrMagenta; + + // + // Pointed Pivot Draw Config ... + eaPointedMajorPivotsDrawConfig.zonesWidth = 2; + eaPointedMajorPivotsDrawConfig.zonesFill = false; + eaPointedMajorPivotsDrawConfig.ignoreZonesAt = true; + eaPointedMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID; + eaPointedMajorPivotsDrawConfig.bullishColor = clrYellow; + eaPointedMajorPivotsDrawConfig.bearishColor = clrOrange; + + // + result = eaMajorPivotDetector != NULL; + if (!result) + { + return result; + } + + // + // Initialize Minor Pivot Detector ... + eaMinorPivotDetector = new XCPivotDetector( + eaAlert, // Alert ... + "X121SDTMinor", // Pivot Prefix ... + false, // Support Inverted Zones ... + 0 // Processable Zones ... + ); + + // + // Register Event Handlers ... + eaMinorPivotDetector.AddOnPeakBreakedHandler(OnMinorPeakBreakedHandler); + eaMinorPivotDetector.AddOnValeBreakedHandler(OnMinorValeBreakedHandler); + eaMinorPivotDetector.AddOnPeakDetectedHandler(OnMinorPeakDetectedHandler); + eaMinorPivotDetector.AddOnValeDetectedHandler(OnMinorValeDetectedHandler); + + // + // Pivot Draw Config ... + eaMinorPivotsDrawConfig.zonesWidth = 1; + eaMinorPivotsDrawConfig.zonesFill = false; + eaMinorPivotsDrawConfig.ignoreZonesAt = true; + eaMinorPivotsDrawConfig.bearishColor = clrRed; + eaMinorPivotsDrawConfig.bullishColor = clrLime; + eaMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT; + + // + // Pointed Pivot Draw Config ... + eaPointedMinorPivotsDrawConfig.zonesWidth = 2; + eaPointedMinorPivotsDrawConfig.zonesFill = false; + eaPointedMinorPivotsDrawConfig.ignoreZonesAt = true; + eaPointedMinorPivotsDrawConfig.bearishColor = clrOrange; + eaPointedMinorPivotsDrawConfig.bullishColor = clrYellow; + eaPointedMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT; + + // + result = eaMinorPivotDetector != NULL; + if (!result) + { + return result; + } + + // + return result; +} + +/** + * Process Pivots ... + */ +void HandleProcessMajorPviots() +{ + // + // 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(eaLastMajorPivotProcessing) || + bar.time > eaLastMajorPivotProcessing; + if (!has) + { + return; + } + + // + eaLastMajorPivotProcessing = bar.time; + + // + eaMajorPivotDetector.Process( + bar, // XOHCL Bar ... + 2, // Pivot Approvation Length ... + 20 // Loopback ... + ); + + // + // Handle Draw Pivots ... + if (!eaDrawMajorPivots) + { + return; + } + + // + XBoxZone tmps[]; + FillNotExists( + tmps, // Dest ... + eaMajorPivotDetector.peaks, // Source ... + false // Force Clean ... + ); + FillNotExists( + tmps, // Dest ... + eaMajorPivotDetector.vales, // Source ... + false // Force Clean ... + ); + if (HasChild(tmps)) + { + eaDrawer.DrawZones( + tmps, // Boxes ... + eaMajorPivotsDrawConfig // Draw Config ... + ); + } + XClean(tmps); +} + +/** + * Process Pivots ... + */ +void HandleProcessMinorPviots() +{ + // + // Initialize Pivot Processing Bar ... + XOHCL bar; + bool has = bar.Init( + _Symbol, + PERIOD_M15, + 1 // Last Completed 4H Bar ... + ); + if (!has) + { + return; + } + + // + // Validate Passing Bar ... + has = + !IsXValid(eaLastMinorPivotProcessing) || + bar.time > eaLastMinorPivotProcessing; + if (!has) + { + return; + } + + // + eaLastMinorPivotProcessing = bar.time; + + // + eaMinorPivotDetector.Process( + bar, // XOHCL Bar ... + 5, // Pivot Approvation Length ... + 40 // Loopback ... + ); + + // + // Handle Draw Pivots ... + if (!eaDrawMinorPivots) + { + return; + } + + // + XBoxZone tmps[]; + FillNotExists( + tmps, // Dest ... + eaMinorPivotDetector.peaks, // Source ... + false // Force Clean ... + ); + FillNotExists( + tmps, // Dest ... + eaMinorPivotDetector.vales, // Source ... + false // Force Clean ... + ); + if (HasChild(tmps)) + { + eaDrawer.DrawZones( + tmps, // Boxes ... + eaMinorPivotsDrawConfig // Draw Config ... + ); + } + XClean(tmps); +} +// #endregion + +// #region Custom Event Handlers ... + +// #region Pivot Detector Event Handlers ... +/** + * Calls when a Peak Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMajorPeakBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a Vale Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMajorValeBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a New Peak Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMajorPeakDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMajorPivotsDrawConfig // + ); + + // + DebugBreak(); +} + +/** + * Calls when a New Vale Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMajorValeDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMajorPivotsDrawConfig // + ); + + // + DebugBreak(); +} + +/** + * Calls when a Peak Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorPeakBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a Vale Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorValeBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a New Peak Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorPeakDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMinorPivotsDrawConfig // + ); + + // + DebugBreak(); +} + +/** + * Calls when a New Vale Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorValeDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMinorPivotsDrawConfig // + ); + + // + 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 diff --git a/Experts/x-saherelm.x-pivot-detector.test.mq5 b/Experts/x-saherelm.x-pivot-detector.test.mq5 index 939f8023..d979bc28 100644 --- a/Experts/x-saherelm.x-pivot-detector.test.mq5 +++ b/Experts/x-saherelm.x-pivot-detector.test.mq5 @@ -30,13 +30,24 @@ XCAlert *eaAlert; XCPOIDrawer *eaDrawer; -CArrayObj eaDrawnObjects; -XCPivotDetector *eaPivotDetector; XCSessionDetector *eaSessionDetector; +// +XCPivotDetector *eaMajorPivotDetector; +XCPivotDetector *eaMinorPivotDetector; + // // Last Processing Pivots Time ... -datetime eaLastPivotProcessing = NULL; +bool eaDrawMajorPivots = true; +bool eaDrawMinorPivots = true; +XPOIDrawConfig eaMajorPivotsDrawConfig; +XPOIDrawConfig eaMinorPivotsDrawConfig; +datetime eaLastMajorPivotProcessing = NULL; +datetime eaLastMinorPivotProcessing = NULL; +XPOIDrawConfig eaPointedMajorPivotsDrawConfig; +XPOIDrawConfig eaPointedMinorPivotsDrawConfig; +ENUM_X_DIRECTION eaMajorBias = X_DIRECTION_NONE; +ENUM_X_DIRECTION eaMinorBias = X_DIRECTION_NONE; // #endregion // #region Global Event Handlers ... @@ -84,12 +95,19 @@ void OnDeinit(const int reason) // Destroy Timer ... // If Initialized ... + // + eaMajorPivotsDrawConfig.Clean(); + eaMinorPivotsDrawConfig.Clean(); + eaPointedMajorPivotsDrawConfig.Clean(); + eaPointedMinorPivotsDrawConfig.Clean(); + // // Cleanup Resources ... ZeroMemory(eaAlert); ZeroMemory(eaDrawer); - ZeroMemory(eaPivotDetector); ZeroMemory(eaSessionDetector); + ZeroMemory(eaMajorPivotDetector); + ZeroMemory(eaMinorPivotDetector); } /** @@ -103,7 +121,12 @@ void OnTick() // // Process Pivot Detector ... - HandleProcessPviots(); + HandleProcessMajorPviots(); + HandleProcessMinorPviots(); + + // + // Detecting Bias ... + HanldeBiasDetection(); } /** @@ -142,8 +165,8 @@ bool InitializeRequirements() // Initialize Alert ... eaAlert = new XCAlert( ShortName, - true, // Enable Alert ... - true, // Log Alert ... + false, // Enable Alert ... + false, // Log Alert ... false, // Terminal Alert ... false, // Mail Alert ... false, // Push Alerts ... @@ -179,20 +202,82 @@ bool InitializeRequirements() } // - // Initialize Pivot Detector ... - eaPivotDetector = new XCPivotDetector( - eaAlert // Alert ... + // Initialize Major Pivot Detector ... + eaMajorPivotDetector = new XCPivotDetector( + eaAlert, // Alert ... + "X121SDTMajor", // Pivot Prefix ... + false, // Support Inverted Zones ... + 0 // Processable Zones ... ); // // Register Event Handlers ... - eaPivotDetector.AddOnPeakBreakedHandler(OnPeakBreakedHandler); - eaPivotDetector.AddOnValeBreakedHandler(OnValeBreakedHandler); - eaPivotDetector.AddOnPeakDetectedHandler(OnPeakDetectedHandler); - eaPivotDetector.AddOnValeDetectedHandler(OnValeDetectedHandler); + eaMajorPivotDetector.AddOnPeakBreakedHandler(OnMajorPeakBreakedHandler); + eaMajorPivotDetector.AddOnValeBreakedHandler(OnMajorValeBreakedHandler); + eaMajorPivotDetector.AddOnPeakDetectedHandler(OnMajorPeakDetectedHandler); + eaMajorPivotDetector.AddOnValeDetectedHandler(OnMajorValeDetectedHandler); // - result = eaPivotDetector != NULL; + // Pivot Draw Config ... + eaMajorPivotsDrawConfig.zonesWidth = 2; + eaMajorPivotsDrawConfig.zonesFill = false; + eaMajorPivotsDrawConfig.ignoreZonesAt = true; + eaMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID; + eaMajorPivotsDrawConfig.bullishColor = clrAqua; + eaMajorPivotsDrawConfig.bearishColor = clrMagenta; + + // + // Pointed Pivot Draw Config ... + eaPointedMajorPivotsDrawConfig.zonesWidth = 2; + eaPointedMajorPivotsDrawConfig.zonesFill = false; + eaPointedMajorPivotsDrawConfig.ignoreZonesAt = true; + eaPointedMajorPivotsDrawConfig.zonesStyle = STYLE_SOLID; + eaPointedMajorPivotsDrawConfig.bullishColor = clrYellow; + eaPointedMajorPivotsDrawConfig.bearishColor = clrOrange; + + // + result = eaMajorPivotDetector != NULL; + if (!result) + { + return result; + } + + // + // Initialize Minor Pivot Detector ... + eaMinorPivotDetector = new XCPivotDetector( + eaAlert, // Alert ... + "X121SDTMinor", // Pivot Prefix ... + false, // Support Inverted Zones ... + 0 // Processable Zones ... + ); + + // + // Register Event Handlers ... + eaMinorPivotDetector.AddOnPeakBreakedHandler(OnMinorPeakBreakedHandler); + eaMinorPivotDetector.AddOnValeBreakedHandler(OnMinorValeBreakedHandler); + eaMinorPivotDetector.AddOnPeakDetectedHandler(OnMinorPeakDetectedHandler); + eaMinorPivotDetector.AddOnValeDetectedHandler(OnMinorValeDetectedHandler); + + // + // Pivot Draw Config ... + eaMinorPivotsDrawConfig.zonesWidth = 1; + eaMinorPivotsDrawConfig.zonesFill = false; + eaMinorPivotsDrawConfig.ignoreZonesAt = true; + eaMinorPivotsDrawConfig.bearishColor = clrRed; + eaMinorPivotsDrawConfig.bullishColor = clrLime; + eaMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT; + + // + // Pointed Pivot Draw Config ... + eaPointedMinorPivotsDrawConfig.zonesWidth = 2; + eaPointedMinorPivotsDrawConfig.zonesFill = false; + eaPointedMinorPivotsDrawConfig.ignoreZonesAt = true; + eaPointedMinorPivotsDrawConfig.bearishColor = clrOrange; + eaPointedMinorPivotsDrawConfig.bullishColor = clrYellow; + eaPointedMinorPivotsDrawConfig.zonesStyle = STYLE_DASHDOT; + + // + result = eaMinorPivotDetector != NULL; if (!result) { return result; @@ -205,7 +290,7 @@ bool InitializeRequirements() /** * Process Pivots ... */ -void HandleProcessPviots() +void HandleProcessMajorPviots() { // // Initialize Pivot Processing Bar ... @@ -213,7 +298,7 @@ void HandleProcessPviots() bool has = bar.Init( _Symbol, PERIOD_H4, - 1 // Last Completed 4H Bar ... + 1 // Last Completed Bar ... ); if (!has) { @@ -223,15 +308,18 @@ void HandleProcessPviots() // // Validate Passing Bar ... has = - !IsXValid(eaLastPivotProcessing) || - bar.time > eaLastPivotProcessing; + !IsXValid(eaLastMajorPivotProcessing) || + bar.time > eaLastMajorPivotProcessing; if (!has) { return; } // - eaPivotDetector.Process( + eaLastMajorPivotProcessing = bar.time; + + // + eaMajorPivotDetector.Process( bar, // XOHCL Bar ... 2, // Pivot Approvation Length ... 20 // Loopback ... @@ -239,30 +327,141 @@ void HandleProcessPviots() // // Handle Draw Pivots ... + if (!eaDrawMajorPivots) + { + return; + } + + // XBoxZone tmps[]; - CArrayObj *objects; FillNotExists( - tmps, // Dest ... - eaPivotDetector.peaks, // Source ... - false // Force Clean ... + tmps, // Dest ... + eaMajorPivotDetector.peaks, // Source ... + false // Force Clean ... ); FillNotExists( - tmps, // Dest ... - eaPivotDetector.vales, // Source ... - false // Force Clean ... + tmps, // Dest ... + eaMajorPivotDetector.vales, // Source ... + false // Force Clean ... ); if (HasChild(tmps)) { - eaDrawer.DrawBoxes( - tmps, // Boxes ... - objects, // Objects to Hold ... - false // Ignore Type Name ... + eaDrawer.DrawZones( + tmps, // Boxes ... + eaMajorPivotsDrawConfig // Draw Config ... ); } XClean(tmps); - AddObjects(objects, eaDrawnObjects); - if (eaDrawnObjects.Total() > 0) { - DebugBreak(); +} + +/** + * Process Pivots ... + */ +void HandleProcessMinorPviots() +{ + // + // Initialize Pivot Processing Bar ... + XOHCL bar; + bool has = bar.Init( + _Symbol, + PERIOD_M15, + 1 // Last Completed Bar ... + ); + if (!has) + { + return; + } + + // + // Validate Passing Bar ... + has = + !IsXValid(eaLastMinorPivotProcessing) || + bar.time > eaLastMinorPivotProcessing; + if (!has) + { + return; + } + + // + eaLastMinorPivotProcessing = bar.time; + + // + eaMinorPivotDetector.Process( + bar, // XOHCL Bar ... + 5, // Pivot Approvation Length ... + 40 // Loopback ... + ); + + // + // Handle Draw Pivots ... + if (!eaDrawMinorPivots) + { + return; + } + + // + XBoxZone tmps[]; + FillNotExists( + tmps, // Dest ... + eaMinorPivotDetector.peaks, // Source ... + false // Force Clean ... + ); + FillNotExists( + tmps, // Dest ... + eaMinorPivotDetector.vales, // Source ... + false // Force Clean ... + ); + if (HasChild(tmps)) + { + eaDrawer.DrawZones( + tmps, // Boxes ... + eaMinorPivotsDrawConfig // Draw Config ... + ); + } + XClean(tmps); +} + +/** + * Detect Bias based on Pivots ... + */ +void HanldeBiasDetection() +{ + // + bool has = false; + + // + // Major Bias ... + has = + eaMajorPivotDetector.HasPeaks() && + eaMajorPivotDetector.HasVales(); + if (has) + { + // + int youngestPeakIDX = eaMajorPivotDetector.GetYoungestPeak(); + int youngestValeIDX = eaMajorPivotDetector.GetYoungestVale(); + has = + IsValidIndex(youngestPeakIDX) && + IsValidIndex(youngestValeIDX); + if (has) + { + // + eaMajorBias = + eaMajorPivotDetector.peaks[youngestPeakIDX].from > eaMajorPivotDetector.vales[youngestValeIDX].from + ? X_DIRECTION_BEARISH + : eaMajorPivotDetector.vales[youngestValeIDX].from > eaMajorPivotDetector.peaks[youngestPeakIDX].from + ? X_DIRECTION_BULLISH + : X_DIRECTION_NONE; + } + } + + // + if (HasDirection(eaMajorBias)) + { + Comment("Major Bias: ", ToXString(eaMajorBias)); + } + else + { + Comment(""); } } // #endregion @@ -275,7 +474,7 @@ void HandleProcessPviots() * * @param box: XBoxZone instance ... */ -void OnPeakBreakedHandler(XBoxZone &box) +void OnMajorPeakBreakedHandler(XBoxZone &box) { // DebugBreak(); } @@ -285,7 +484,7 @@ void OnPeakBreakedHandler(XBoxZone &box) * * @param box: XBoxZone instance ... */ -void OnValeBreakedHandler(XBoxZone &box) +void OnMajorValeBreakedHandler(XBoxZone &box) { // DebugBreak(); } @@ -295,9 +494,16 @@ void OnValeBreakedHandler(XBoxZone &box) * * @param box: XBoxZone instance ... */ -void OnPeakDetectedHandler(XBoxZone &box) +void OnMajorPeakDetectedHandler(XBoxZone &box) { - // DebugBreak(); + // + eaDrawer.DrawZone( + box, // + eaPointedMajorPivotsDrawConfig // + ); + + // + DebugBreak(); } /** @@ -305,10 +511,71 @@ void OnPeakDetectedHandler(XBoxZone &box) * * @param box: XBoxZone instance ... */ -void OnValeDetectedHandler(XBoxZone &box) +void OnMajorValeDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMajorPivotsDrawConfig // + ); + + // + DebugBreak(); +} + +/** + * Calls when a Peak Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorPeakBreakedHandler(XBoxZone &box) { // DebugBreak(); } + +/** + * Calls when a Vale Breaked ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorValeBreakedHandler(XBoxZone &box) +{ + // DebugBreak(); +} + +/** + * Calls when a New Peak Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorPeakDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMinorPivotsDrawConfig // + ); + + // + DebugBreak(); +} + +/** + * Calls when a New Vale Detected ... + * + * @param box: XBoxZone instance ... + */ +void OnMinorValeDetectedHandler(XBoxZone &box) +{ + // + eaDrawer.DrawZone( + box, // + eaPointedMinorPivotsDrawConfig // + ); + + // + DebugBreak(); +} // #endregion // #region Session Detector Event Handlers ... diff --git a/Libraries/x-saherelm.x-poi.extensions.lib.mq5 b/Libraries/x-saherelm.x-poi.extensions.lib.mq5 index 93739be6..1ddb35f2 100644 --- a/Libraries/x-saherelm.x-poi.extensions.lib.mq5 +++ b/Libraries/x-saherelm.x-poi.extensions.lib.mq5 @@ -3722,6 +3722,125 @@ bool IsBoxValid( return result; } +/** + * Validate a Box based on Price ... + * + * @param box: XBoxZone instance, box to Validate ... + * @param barIndex: int, Start Validating Bar's Index ... + * @param pType: ENUM_X_BOUNDARY_PRICE, Price Type for Validation ... + * + * @return ( bool ) + */ +bool IsBoxValid( + XBoxZone &box, + int barIndex = 0, + ENUM_X_BOUNDARY_PRICE pType = X_BOUNDARY_PRICE_UP_DOWN // +) +{ + // + bool result = false; + + // + // Normalize Bar Index ... + barIndex = NormalizeInt(barIndex, 0); + + // + // Validaet ... + result = + box.IsValid() && + IsXValid(pType); + if (!result) + { + return result; + } + + // + bool isBullish = box.IsBullish(); + + // + int fromIDX = box.FromIndex(); + result = + IsValidIndex(fromIDX) && + barIndex < fromIDX; + if (!result) + { + return result; + } + + // + // Loop through ... + XOHCL iBar; + bool has = false; + double appliedPrice = EMPTY_VALUE; + for (int i = barIndex; i < fromIDX; i++) + { + // + // Initialize Bar ... + iBar.Clean(); + has = iBar.Init(box.symbol, box.period, i); + if (!has) + { + // + result = false; + break; + } + + // + // Calculate Applied Price ... + switch (pType) + { + // + case X_BOUNDARY_PRICE_UP_DOWN: + // + appliedPrice = + isBullish + ? iBar.GetDown() + : iBar.GetUp(); + break; + + // + case X_BOUNDARY_PRICE_HIGH_LOW: + // + appliedPrice = + isBullish + ? iBar.low + : iBar.high; + break; + + // + default: + appliedPrice = EMPTY_VALUE; + break; + } + has = NotEmptyZero(appliedPrice); + if (!has) + { + // + result = false; + break; + } + + // + has = + has && + result && + (isBullish + ? appliedPrice >= box.lower + : appliedPrice <= box.upper); + if (!has) { + // + result = false; + break; + } + + // + iBar.Clean(); + } + + // + return result; +} + // void SortBoxes( XBoxZone &items[], diff --git a/XSDTContextGenerator.js b/XSDTContextGenerator.js new file mode 100644 index 00000000..5a420f4c --- /dev/null +++ b/XSDTContextGenerator.js @@ -0,0 +1,298 @@ +#!/usr/bin/env node + +/** + * Context Generator Module ... + * a module for Generating Context ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) +*/ + +// +//#region Imports ... +const fs = require("fs"); +const path = require("path"); +//#endregion + +// +//#region Constants ... +const outputFilePrefix = "x121XSDT"; +const allowedPatterns = [ + // + // Base Pilot Files ... + { + kind: "file", + mode: "allow", + match: "name", + pattern: /^x-saherelm.pivot-detector.class.mq5$/i + }, + { + kind: "file", + mode: "allow", + match: "name", + pattern: /^x-saherelm.x-pivot-detector.test.mq5$/i + }, +] +//#endregion + +// +// Reading Required Info +const inputPath = process.argv[2]; +const searchPath = path.resolve(inputPath || process.cwd()); + +// Check Given Path is Valid or not ... +if (!fs.existsSync(searchPath)) { + console.error(`Error: path not found: ${searchPath}`); + process.exit(1); +} + +// +// Retrieve Stats of Given Path ... +const stat = fs.statSync(searchPath); +if (!stat.isDirectory()) { + console.error(`Error: path is not a directory: ${searchPath}`); + process.exit(1); +} + +// +// Generate Required Data ... +const timestamp = getTimestamp(new Date()); +const folderName = path.basename(searchPath) || "root"; +const safeFolderName = sanitizeFileName(folderName); + +// Generate Output file Name ... +const outputFileName = inputPath + ? `${outputFilePrefix}-${safeFolderName}-${timestamp}.txt` + : `${outputFilePrefix}-${timestamp}.txt`; + +// +const outputPath = path.join( + process.cwd(), + "Documents", + "GeneratedContext", + outputFileName +); + +// Create Output File ... +fs.writeFileSync(outputPath, "", "utf8"); + +// Navigate to Context Generation ... +walk(searchPath); + +// +console.log(`Context generated: ${outputPath}`); + +// +//#region Required Tools ... +/** + * Validate Timestamp ... + * + * @param {*} value + * @returns + */ +function pad(value) { + return String(value).padStart(2, "0"); +} + +/** + * Retrieve Validate ... + * + * @param {*} date + * @returns + */ +function getTimestamp(date) { + // + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + const seconds = pad(date.getSeconds()); + + // + return `${year}${month}${day}_${hours}${minutes}${seconds}`; +} + +/** + * Retrieve Leaf Folder ... + * + * @param {*} value + * @returns + */ +function sanitizeFileName(value) { + return value.replace(/[\\/:*?"<>|]/g, "_"); +} + +/** + * Match Value ... + * + * @param {*} rule + * @param {*} targetPath + * @returns + */ +function getMatchValue(rule, targetPath) { + // + const normalizedPath = targetPath.replace(/\\/g, "/"); + if (rule.match === "path") { + return normalizedPath; + } + + // + return path.basename(targetPath); +} + +/** + * Match Patterns ... + * + * @param {*} targetPath + * @param {*} rules + * @returns + */ +function matchesPatterns(targetPath, rules) { + // + return rules.some(rule => { + const value = getMatchValue(rule, targetPath); + return rule.pattern.test(value); + }); +} + +/** + * Check a Directory Path is Valid for Parsing or not ... + * + * @param {*} fileName + * @returns + */ +function isAllowedFile(filePath) { + // + // Extract Deny Rules ... + const denyRules = allowedPatterns.filter( + p => p.kind === "file" && p.mode === "deny" + ); + + // + // Extract Allowed Rules ... + const allowRules = allowedPatterns.filter( + p => p.kind === "file" && p.mode === "allow" + ); + + // Check Deny Rules First ... + if (matchesPatterns(filePath, denyRules)) { + return false; + } + + // Check Allowed Rules ... + return matchesPatterns(filePath, allowRules); +} + +/** + * Check a Directory Path is Valid for Parsing or not ... + * + * @param {*} directoryPath + * @returns + */ +function isAllowedDirectory(directoryPath) { + // + // Extract Deny Rules ... + const denyRules = allowedPatterns.filter( + p => p.kind === "directory" && p.mode === "deny" + ); + + // + // Extract Allowed Rules ... + const allowRules = allowedPatterns.filter( + p => p.kind === "directory" && p.mode === "allow" + ); + + // + // Check Deny Patterns as First ... + if (matchesPatterns(directoryPath, denyRules)) { + return false; + } + + // + // Check Allow Patterns Exists ... + if (allowRules.length === 0) { + return true; + } + + // + // Chck Allowed Patterns Matched ... + return matchesPatterns(directoryPath, allowRules); +} + +/** + * Loop through a Directory and Navigate files for including in Context ... + * + * @param {*} dir + * @returns + */ +function walk(dir) { + // + // Reading Directory Content ... + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch (error) { + fs.appendFileSync( + outputPath, + `\n### ERROR reading directory: ${dir}\n${error.message}\n`, + "utf8" + ); + return; + } + + // + // Loop through Entries ... + for (const entry of entries) { + // + const fullPath = path.join(dir, entry.name); + + // + // Ignore if Symlink ... + if (entry.isSymbolicLink()) { + continue; + } + + // + // Handle if requested Path is Directory ... + if (entry.isDirectory()) { + // + // Allowed Folders ... + if (!isAllowedDirectory(fullPath)) { + continue; + } + + // + walk(fullPath); + } + else + // + // Handle if requested Path is File ... + if (entry.isFile()) { + // + // Allowed File ... + if (!isAllowedFile(fullPath)) { + continue; + } + + // + // Holds File Content ... + let content; + try { + content = fs.readFileSync(fullPath, "utf8"); + } catch (error) { + content = `// Error reading file: ${error.message}`; + } + + // + // Append File Content for Context ... + fs.appendFileSync( + outputPath, + `\n### FILE: ${fullPath}\n\n${content}\n`, + "utf8" + ); + } + } +} +//#endregion