//+------------------------------------------------------------------+ //| SignalDTDB.mqh | //+------------------------------------------------------------------+ #property copyright "AnimateDread" #property version "1.0" #property description "Double Tops and Bottoms Signal" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Expert\ExpertSignalCustom.mqh" //+------------------------------------------------------------------+ //| CSignalDTDB class. | //| Purpose: Class for detecting double tops and bottoms. | //| Inherits from CExpertSignalCustom. | //+------------------------------------------------------------------+ class CSignalDTDB : public CExpertSignalCustom { protected: // Pattern weights int m_pattern_0; // Variables for dynamic filtering int m_lookback_period; // Lookback period to calculate average range public: CSignalDTDB(); ~CSignalDTDB(); // Methods for adjusting pattern weights void Pattern_0(int value) { m_pattern_0 = value; } void LookBackPeriod(int value) { m_lookback_period = value; } virtual void ApplyPatternWeight(int patternNumber, int weight); // Method to validate parameters virtual bool ValidationSettings(); // Methods to generate signals virtual int LongCondition(void); virtual int ShortCondition(void); protected: // Method to detect double tops bool DetectDoubleTop(int startIdx); // Method to detect double bottoms bool DetectDoubleBottom(int startIdx); // Helper method to detect peaks and troughs bool IsExtreme(int idx, double prevExtreme1, double prevExtreme2, bool isPeak) { double currentValue = isPeak ? High(idx) : Low(idx); return isPeak ? (currentValue > prevExtreme1 && currentValue > prevExtreme2) : (currentValue < prevExtreme1 && currentValue < prevExtreme2); } // Common method to detect Double Top or Bottom bool DetectDoubleExtreme(int startIdx, bool isTop, double threshold = 0.02, int symmetryThreshold = 2) { int extremesFound = 0; double extreme1 = 0, extreme2 = 0; int extreme1Idx = 0, extreme2Idx = 0; double prevExtreme1 = isTop ? -DBL_MAX : DBL_MAX, prevExtreme2 = prevExtreme1; int trendAnalysisStartIdx = MathMax(0, startIdx - m_lookback_period); for(int i = startIdx; i >= trendAnalysisStartIdx; i--) { double value = isTop ? High(i) : Low(i); if(extremesFound == 0 && IsExtreme(i, prevExtreme1, prevExtreme2, isTop)) { extreme1 = value; extreme1Idx = i; extremesFound++; } else if(extremesFound == 1 && IsExtreme(i, prevExtreme1, prevExtreme2, isTop)) { extreme2 = value; extreme2Idx = i; extremesFound++; if(fabs(extreme1 - extreme2) / fabs(extreme1) < threshold) { double extremeDistance = fabs(extreme2Idx - extreme1Idx); double oppositeDuration = fabs(extreme1Idx - startIdx); if(fabs(extremeDistance - oppositeDuration) < symmetryThreshold) { double neckline = (extreme1 + extreme2) / 2; if((isTop && Low(startIdx) < neckline) || (!isTop && High(startIdx) > neckline)) { return true; } } } // Reset extremesFound to 1 to check for another potential double top/bottom extremesFound = 1; extreme1 = extreme2; extreme1Idx = extreme2Idx; } prevExtreme2 = prevExtreme1; prevExtreme1 = value; } return false; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSignalDTDB::CSignalDTDB() : m_pattern_0(30), m_lookback_period(10) { m_id = "DTDB"; m_pattern_count = 1; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSignalDTDB::~CSignalDTDB(void) { } //+------------------------------------------------------------------+ //| Validation settings | //+------------------------------------------------------------------+ bool CSignalDTDB::ValidationSettings() { if(!CExpertSignalCustom::ValidationSettings()) return false; if(m_lookback_period <= 0 || m_lookback_period > 200) { Print("Lookback Period must be greater than 0 and smaller than 200"); return false; } return true; } //+------------------------------------------------------------------+ //| Detect Double Top | //+------------------------------------------------------------------+ bool CSignalDTDB::DetectDoubleTop(int startIdx) { return DetectDoubleExtreme(startIdx, true); } //+------------------------------------------------------------------+ //| Detect Double Bottom | //+------------------------------------------------------------------+ bool CSignalDTDB::DetectDoubleBottom(int startIdx) { return DetectDoubleExtreme(startIdx, false); } //+------------------------------------------------------------------+ //| Long Condition | //+------------------------------------------------------------------+ int CSignalDTDB::LongCondition(void) { int result = 0; int idx = StartIndex(); if(IS_PATTERN_USAGE(0) && DetectDoubleBottom(idx)) { result = m_pattern_0; m_active_pattern = "Pattern_0"; } if(result != 0) { m_active_direction = "Buy"; } return result; } //+------------------------------------------------------------------+ //| Short Condition | //+------------------------------------------------------------------+ int CSignalDTDB::ShortCondition(void) { int result = 0; int idx = StartIndex(); if(IS_PATTERN_USAGE(0) && DetectDoubleTop(idx)) { result = m_pattern_0; m_active_pattern = "Pattern_0"; } if(result != 0) { m_active_direction = "Sell"; } return result; } //+------------------------------------------------------------------+ //| Apply Pattern Weight | //+------------------------------------------------------------------+ void CSignalDTDB::ApplyPatternWeight(int patternNumber, int weight) { switch(patternNumber) { default: break; case 0: Pattern_0(weight); break; } } //+------------------------------------------------------------------+