//+------------------------------------------------------------------+ //| CandlePatternSearch.mq5| //| Copyright 2025, MetaQuotes Ltd.| //| https://www.mql5.com/en/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property description "Advanced candlestick pattern detection with monitoring and alerts" #property strict //--- Input Parameters input int LookbackPeriod = 1000; input color PatternColor = clrDodgerBlue; input int MaxDisplayPatterns = 100; input bool EnableAlerts = true; input bool SoundAlerts = true; input bool PushNotifications = false; input bool EmailAlerts = false; input int AlertCooldown = 60; //--- Global Variables int searchEdit; int searchBtn; int scanAllBtn; int clearBtn; int monitorBtn; int resultsLabel; int alertLabel; int panelBg; string lastSearchQuery = ""; int totalPatternsFound = 0; datetime lastScanTime = 0; bool monitoringEnabled = false; int newPatternsDetected = 0; datetime lastAlertTime = 0; // Alert tracking string lastAlertPattern = ""; datetime lastAlertTimeForPattern = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("Initializing Candle Pattern Search Engine with Monitoring..."); // Create GUI above the grid CreateGUI(); // Perform initial pattern scan ScanAllPatterns(); Print("Candle Pattern Search Engine initialized successfully"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update GUI elements if needed UpdateResultsDisplay(); // Real-time pattern monitoring if(monitoringEnabled) { MonitorNewPatterns(); } } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("Deinitializing Candle Pattern Search Engine..."); // Clean up all graphical objects ObjectsDeleteAll(0, "PATTERN_"); ObjectsDeleteAll(0, "SEARCH_"); Print("Candle Pattern Search Engine deinitialized"); } //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Handle button clicks if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == "SEARCH_BTN") { string query = ObjectGetString(0, "SEARCH_EDIT", OBJPROP_TEXT); SearchPatterns(query); } else if(sparam == "SCAN_ALL_BTN") { ScanAllPatterns(); } else if(sparam == "CLEAR_BTN") { ClearPatterns(); } else if(sparam == "MONITOR_BTN") { ToggleMonitoring(); } } } //+------------------------------------------------------------------+ //| Create GUI elements above the grid | //+------------------------------------------------------------------+ void CreateGUI() { int x = 10; int y = 50; int width = 300; int height = 200; // Increased height for monitoring status // Create background panel panelBg = CreateRectangle("SEARCH_PANEL", x, y, width, height, clrDarkSlateGray, clrGray, false); // Create search label CreateLabel("SEARCH_LABEL", x + 10, y + 10, "🔍 Pattern Search:", clrWhite, 10); // Create search edit box searchEdit = CreateEditBox("SEARCH_EDIT", x + 10, y + 35, 200, 20, "Type pattern name..."); // Create buttons searchBtn = CreateButton("SEARCH_BTN", x + 10, y + 65, 70, 25, "Search", clrDodgerBlue); scanAllBtn = CreateButton("SCAN_ALL_BTN", x + 90, y + 65, 70, 25, "Scan All", clrForestGreen); clearBtn = CreateButton("CLEAR_BTN", x + 170, y + 65, 70, 25, "Clear", clrCrimson); monitorBtn = CreateButton("MONITOR_BTN", x + 10, y + 95, 230, 25, "🔴 Start Monitoring", clrOrange); // Create results display resultsLabel = CreateLabel("RESULTS_LABEL", x + 10, y + 125, "📊 Total Patterns: 0\n⏰ Last Scan: Never", clrWhite, 9); alertLabel = CreateLabel("ALERT_LABEL", x + 10, y + 165, "🔔 Monitoring: OFF\nNew Patterns: 0", clrSilver, 8); } //+------------------------------------------------------------------+ //| Toggle real-time monitoring | //+------------------------------------------------------------------+ void ToggleMonitoring() { monitoringEnabled = !monitoringEnabled; if(monitoringEnabled) { ObjectSetString(0, "MONITOR_BTN", OBJPROP_TEXT, "🟢 Monitoring Active"); ObjectSetInteger(0, "MONITOR_BTN", OBJPROP_BGCOLOR, clrLimeGreen); newPatternsDetected = 0; Print("Real-time pattern monitoring activated"); // Initial monitoring scan MonitorNewPatterns(); } else { ObjectSetString(0, "MONITOR_BTN", OBJPROP_TEXT, "🔴 Start Monitoring"); ObjectSetInteger(0, "MONITOR_BTN", OBJPROP_BGCOLOR, clrOrange); Print("Real-time pattern monitoring deactivated"); } UpdateResultsDisplay(); } //+------------------------------------------------------------------+ //| Monitor for new patterns in real-time | //+------------------------------------------------------------------+ void MonitorNewPatterns() { static datetime lastBarTime = 0; datetime currentBarTime = iTime(_Symbol, _Period, 0); // Check if new bar has formed if(currentBarTime != lastBarTime) { lastBarTime = currentBarTime; // Check recent candles for new patterns (last 5 candles) int patternsFound = ScanRecentCandles(5); if(patternsFound > 0) { newPatternsDetected += patternsFound; UpdateResultsDisplay(); Print("Detected ", patternsFound, " new pattern(s) on current bar"); } } } //+------------------------------------------------------------------+ //| Scan recent candles for new patterns | //+------------------------------------------------------------------+ int ScanRecentCandles(int candlesToCheck) { int newPatterns = 0; // Check only the most recent candles for(int i = 0; i < candlesToCheck; i++) { // Check all patterns on this candle string patterns[] = { "doji", "hammer", "inverted hammer", "hanging man", "shooting star", "bullish engulfing", "bearish engulfing", "piercing line", "dark cloud cover", "morning star", "evening star" }; for(int p = 0; p < ArraySize(patterns); p++) { if(CheckPatternAtBar(patterns[p], i)) { // Check if this pattern was already alerted recently if(CanAlertPattern(patterns[p])) { MarkPatternWithRectangle(patterns[p], i); TriggerAlert(patterns[p], i); newPatterns++; totalPatternsFound++; // Update last alert time for this pattern lastAlertTimeForPattern = TimeCurrent(); lastAlertPattern = patterns[p]; } } } } return newPatterns; } //+------------------------------------------------------------------+ //| Check if we can alert for this pattern (cooldown) | //+------------------------------------------------------------------+ bool CanAlertPattern(string patternName) { // If it's a different pattern, allow alert if(patternName != lastAlertPattern) return true; // If it's the same pattern, check cooldown if(TimeCurrent() - lastAlertTimeForPattern >= AlertCooldown) { return true; } return false; } //+------------------------------------------------------------------+ //| Trigger alert for detected pattern | //+------------------------------------------------------------------+ void TriggerAlert(string patternName, int barIndex) { if(!EnableAlerts) return; string alertMessage = StringFormat("🎯 PATTERN DETECTED: %s on %s %s", patternName, _Symbol, EnumToString(_Period)); string fullMessage = StringFormat("%s\nTime: %s", alertMessage, TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES)); // Main alert Alert(alertMessage); // Sound alert if(SoundAlerts) { PlaySound("alert.wav"); // Make sure this sound file exists in Sounds folder } // Push notification if(PushNotifications) { SendNotification(fullMessage); } // Email alert if(EmailAlerts) { SendMail("Candle Pattern Alert - " + _Symbol, fullMessage); } Print("ALERT: ", fullMessage); lastAlertTime = TimeCurrent(); } //+------------------------------------------------------------------+ //| Search for specific patterns | //+------------------------------------------------------------------+ void SearchPatterns(string query) { if(query == "Type pattern name...") query = ""; Print("Searching for: ", query); lastSearchQuery = query; // Clear previous patterns ObjectsDeleteAll(0, "PATTERN_"); totalPatternsFound = 0; newPatternsDetected = 0; if(query == "") { ScanAllPatterns(); return; } // Convert query to lowercase for case-insensitive search string queryLower = ConvertToLowercase(query); // Search through candles for(int i = 1; i < LookbackPeriod; i++) { if(CheckPatternAtBar(queryLower, i)) { MarkPatternWithRectangle(queryLower, i); totalPatternsFound++; } } lastScanTime = TimeCurrent(); UpdateResultsDisplay(); Print("Found ", totalPatternsFound, " instances of '", query, "'"); } //+------------------------------------------------------------------+ //| Scan for all patterns | //+------------------------------------------------------------------+ void ScanAllPatterns() { Print("Scanning all patterns..."); // Clear previous patterns ObjectsDeleteAll(0, "PATTERN_"); totalPatternsFound = 0; newPatternsDetected = 0; // Comprehensive pattern list string patterns[] = { // Single candle patterns "doji", "hammer", "inverted hammer", "hanging man", "shooting star", "spinning top", "marubozu", // Two candle patterns "bullish engulfing", "bearish engulfing", "piercing line", "dark cloud cover", "bullish harami", "bearish harami", "tweezer bottom", "tweezer top", // Three candle patterns "morning star", "evening star", "three white soldiers", "three black crows" }; for(int p = 0; p < ArraySize(patterns); p++) { for(int i = 1; i < LookbackPeriod; i++) { if(CheckPatternAtBar(patterns[p], i)) { MarkPatternWithRectangle(patterns[p], i); totalPatternsFound++; } } } lastScanTime = TimeCurrent(); UpdateResultsDisplay(); Print("Scan complete. Found ", totalPatternsFound, " total patterns"); } //+------------------------------------------------------------------+ //| Check pattern at specific bar | //+------------------------------------------------------------------+ bool CheckPatternAtBar(string patternName, int barIndex) { MqlRates rates[]; ArraySetAsSeries(rates, true); // Get required number of candles int candlesNeeded = GetCandlesNeeded(patternName); if(CopyRates(_Symbol, _Period, barIndex, candlesNeeded, rates) < candlesNeeded) return false; // Check specific pattern if(patternName == "doji") return CheckDoji(rates); else if(patternName == "hammer") return CheckHammer(rates); else if(patternName == "inverted hammer") return CheckInvertedHammer(rates); else if(patternName == "hanging man") return CheckHangingMan(rates); else if(patternName == "shooting star") return CheckShootingStar(rates); else if(patternName == "spinning top") return CheckSpinningTop(rates); else if(patternName == "marubozu") return CheckMarubozu(rates); else if(patternName == "bullish engulfing") return CheckBullishEngulfing(rates); else if(patternName == "bearish engulfing") return CheckBearishEngulfing(rates); else if(patternName == "piercing line") return CheckPiercingLine(rates); else if(patternName == "dark cloud cover") return CheckDarkCloudCover(rates); else if(patternName == "bullish harami") return CheckBullishHarami(rates); else if(patternName == "bearish harami") return CheckBearishHarami(rates); else if(patternName == "tweezer bottom") return CheckTweezerBottom(rates); else if(patternName == "tweezer top") return CheckTweezerTop(rates); else if(patternName == "morning star") return CheckMorningStar(rates); else if(patternName == "evening star") return CheckEveningStar(rates); else if(patternName == "three white soldiers") return CheckThreeWhiteSoldiers(rates); else if(patternName == "three black crows") return CheckThreeBlackCrows(rates); return false; } //+------------------------------------------------------------------+ //| Get candles needed for pattern | //+------------------------------------------------------------------+ int GetCandlesNeeded(string patternName) { // Single candle patterns if(patternName == "doji") return 1; if(patternName == "hammer") return 1; if(patternName == "inverted hammer") return 1; if(patternName == "hanging man") return 1; if(patternName == "shooting star") return 1; if(patternName == "spinning top") return 1; if(patternName == "marubozu") return 1; // Two candle patterns if(patternName == "bullish engulfing") return 2; if(patternName == "bearish engulfing") return 2; if(patternName == "piercing line") return 2; if(patternName == "dark cloud cover") return 2; if(patternName == "bullish harami") return 2; if(patternName == "bearish harami") return 2; if(patternName == "tweezer bottom") return 2; if(patternName == "tweezer top") return 2; // Three candle patterns if(patternName == "morning star") return 3; if(patternName == "evening star") return 3; if(patternName == "three white soldiers") return 3; if(patternName == "three black crows") return 3; return 1; } //+------------------------------------------------------------------+ //| Pattern recognition functions - COMPLETE IMPLEMENTATION | //+------------------------------------------------------------------+ bool CheckDoji(MqlRates &rates[]) { if(ArraySize(rates) < 1) return false; double bodySize = MathAbs(rates[0].open - rates[0].close); double totalRange = rates[0].high - rates[0].low; if(totalRange == 0) return false; return (bodySize / totalRange < 0.1); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckHammer(MqlRates &rates[]) { if(ArraySize(rates) < 1) return false; double bodySize = MathAbs(rates[0].open - rates[0].close); double totalRange = rates[0].high - rates[0].low; double lowerShadow = MathMin(rates[0].open, rates[0].close) - rates[0].low; double upperShadow = rates[0].high - MathMax(rates[0].open, rates[0].close); if(totalRange == 0) return false; return (bodySize / totalRange < 0.3 && lowerShadow >= 2 * bodySize && upperShadow <= bodySize * 0.5); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckInvertedHammer(MqlRates &rates[]) { if(ArraySize(rates) < 1) return false; double bodySize = MathAbs(rates[0].open - rates[0].close); double totalRange = rates[0].high - rates[0].low; double lowerShadow = MathMin(rates[0].open, rates[0].close) - rates[0].low; double upperShadow = rates[0].high - MathMax(rates[0].open, rates[0].close); if(totalRange == 0) return false; return (bodySize / totalRange < 0.3 && upperShadow >= 2 * bodySize && lowerShadow <= bodySize * 0.5); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckHangingMan(MqlRates &rates[]) { return CheckHammer(rates); // Same formation, different context } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckShootingStar(MqlRates &rates[]) { return CheckInvertedHammer(rates); // Same formation, different context } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckSpinningTop(MqlRates &rates[]) { if(ArraySize(rates) < 1) return false; double bodySize = MathAbs(rates[0].open - rates[0].close); double totalRange = rates[0].high - rates[0].low; double lowerShadow = MathMin(rates[0].open, rates[0].close) - rates[0].low; double upperShadow = rates[0].high - MathMax(rates[0].open, rates[0].close); if(totalRange == 0) return false; return (bodySize / totalRange < 0.3 && lowerShadow > bodySize && upperShadow > bodySize); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckMarubozu(MqlRates &rates[]) { if(ArraySize(rates) < 1) return false; double bodySize = MathAbs(rates[0].open - rates[0].close); double totalRange = rates[0].high - rates[0].low; if(totalRange == 0) return false; return (bodySize / totalRange > 0.9); // Very small or no shadows } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckBullishEngulfing(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bearish (red) bool firstBearish = (rates[1].close < rates[1].open); // Second candle must be bullish (green) bool secondBullish = (rates[0].close > rates[0].open); if(!firstBearish || !secondBullish) return false; // Second candle must completely engulf first candle's body bool engulfsBody = (rates[0].open < rates[1].close) && (rates[0].close > rates[1].open); // Second candle should have larger body bool largerBody = (MathAbs(rates[0].close - rates[0].open) > MathAbs(rates[1].close - rates[1].open)); return (engulfsBody && largerBody); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckBearishEngulfing(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bullish (green) bool firstBullish = (rates[1].close > rates[1].open); // Second candle must be bearish (red) bool secondBearish = (rates[0].close < rates[0].open); if(!firstBullish || !secondBearish) return false; // Second candle must completely engulf first candle's body bool engulfsBody = (rates[0].open > rates[1].close) && (rates[0].close < rates[1].open); // Second candle should have larger body bool largerBody = (MathAbs(rates[0].close - rates[0].open) > MathAbs(rates[1].close - rates[1].open)); return (engulfsBody && largerBody); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckPiercingLine(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bearish bool firstBearish = (rates[1].close < rates[1].open); // Second candle must be bullish bool secondBullish = (rates[0].close > rates[0].open); if(!firstBearish || !secondBullish) return false; // Second candle opens below first candle's low bool opensBelow = (rates[0].open < rates[1].low); // Second candle closes above the midpoint of first candle's body double firstMidpoint = rates[1].open + (rates[1].close - rates[1].open) / 2; bool closesAboveMidpoint = (rates[0].close > firstMidpoint); // But doesn't close above first candle's open bool belowFirstOpen = (rates[0].close < rates[1].open); return (opensBelow && closesAboveMidpoint && belowFirstOpen); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckDarkCloudCover(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bullish bool firstBullish = (rates[1].close > rates[1].open); // Second candle must be bearish bool secondBearish = (rates[0].close < rates[0].open); if(!firstBullish || !secondBearish) return false; // Second candle opens above first candle's high bool opensAbove = (rates[0].open > rates[1].high); // Second candle closes below the midpoint of first candle's body double firstMidpoint = rates[1].open + (rates[1].close - rates[1].open) / 2; bool closesBelowMidpoint = (rates[0].close < firstMidpoint); // But doesn't close below first candle's close bool aboveFirstClose = (rates[0].close > rates[1].close); return (opensAbove && closesBelowMidpoint && aboveFirstClose); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckBullishHarami(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bearish and large bool firstBearish = (rates[1].close < rates[1].open); double firstBody = MathAbs(rates[1].close - rates[1].open); // Second candle must be bullish and small, completely inside first candle's body bool secondBullish = (rates[0].close > rates[0].open); double secondBody = MathAbs(rates[0].close - rates[0].open); if(!firstBearish || !secondBullish) return false; // Second candle must be completely inside first candle's body bool insideBody = (rates[0].open > rates[1].close) && (rates[0].close < rates[1].open); // First candle should have significantly larger body bool largerBody = (firstBody > secondBody * 1.5); return (insideBody && largerBody); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckBearishHarami(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // First candle must be bullish and large bool firstBullish = (rates[1].close > rates[1].open); double firstBody = MathAbs(rates[1].close - rates[1].open); // Second candle must be bearish and small, completely inside first candle's body bool secondBearish = (rates[0].close < rates[0].open); double secondBody = MathAbs(rates[0].close - rates[0].open); if(!firstBullish || !secondBearish) return false; // Second candle must be completely inside first candle's body bool insideBody = (rates[0].open < rates[1].close) && (rates[0].close > rates[1].open); // First candle should have significantly larger body bool largerBody = (firstBody > secondBody * 1.5); return (insideBody && largerBody); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckTweezerBottom(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // Both candles should have similar lows bool similarLows = (MathAbs(rates[0].low - rates[1].low) < (rates[1].high - rates[1].low) * 0.1); // First candle should be bearish, second bullish (ideal scenario) bool firstBearish = (rates[1].close < rates[1].open); bool secondBullish = (rates[0].close > rates[0].open); return (similarLows && firstBearish && secondBullish); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckTweezerTop(MqlRates &rates[]) { if(ArraySize(rates) < 2) return false; // Both candles should have similar highs bool similarHighs = (MathAbs(rates[0].high - rates[1].high) < (rates[1].high - rates[1].low) * 0.1); // First candle should be bullish, second bearish (ideal scenario) bool firstBullish = (rates[1].close > rates[1].open); bool secondBearish = (rates[0].close < rates[0].open); return (similarHighs && firstBullish && secondBearish); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckMorningStar(MqlRates &rates[]) { if(ArraySize(rates) < 3) return false; // First candle: long bearish bool firstBearish = (rates[2].close < rates[2].open); double firstBody = MathAbs(rates[2].close - rates[2].open); // Second candle: small body (doji or spinning top) double secondBody = MathAbs(rates[1].close - rates[1].open); double secondRange = rates[1].high - rates[1].low; bool smallSecondBody = (secondBody / secondRange < 0.3); // Third candle: long bullish that closes well into first candle's body bool thirdBullish = (rates[0].close > rates[0].open); double thirdBody = MathAbs(rates[0].close - rates[0].open); // Third candle should close above midpoint of first candle double firstMidpoint = (rates[2].open + rates[2].close) / 2; bool closesAboveMidpoint = (rates[0].close > firstMidpoint); return (firstBearish && smallSecondBody && thirdBullish && closesAboveMidpoint); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckEveningStar(MqlRates &rates[]) { if(ArraySize(rates) < 3) return false; // First candle: long bullish bool firstBullish = (rates[2].close > rates[2].open); double firstBody = MathAbs(rates[2].close - rates[2].open); // Second candle: small body (doji or spinning top) double secondBody = MathAbs(rates[1].close - rates[1].open); double secondRange = rates[1].high - rates[1].low; bool smallSecondBody = (secondBody / secondRange < 0.3); // Third candle: long bearish that closes well into first candle's body bool thirdBearish = (rates[0].close < rates[0].open); double thirdBody = MathAbs(rates[0].close - rates[0].open); // Third candle should close below midpoint of first candle double firstMidpoint = (rates[2].open + rates[2].close) / 2; bool closesBelowMidpoint = (rates[0].close < firstMidpoint); return (firstBullish && smallSecondBody && thirdBearish && closesBelowMidpoint); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckThreeWhiteSoldiers(MqlRates &rates[]) { if(ArraySize(rates) < 3) return false; for(int i = 0; i < 3; i++) { // Each candle must be bullish with long body if(rates[i].close <= rates[i].open) return false; double body = rates[i].close - rates[i].open; double range = rates[i].high - rates[i].low; if(body / range < 0.6) return false; // Long body requirement // Each subsequent candle should open within previous candle's body if(i > 0 && rates[i].open < rates[i-1].open) return false; // Each candle should close higher than previous if(i > 0 && rates[i].close <= rates[i-1].close) return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckThreeBlackCrows(MqlRates &rates[]) { if(ArraySize(rates) < 3) return false; for(int i = 0; i < 3; i++) { // Each candle must be bearish with long body if(rates[i].close >= rates[i].open) return false; double body = rates[i].open - rates[i].close; double range = rates[i].high - rates[i].low; if(body / range < 0.6) return false; // Long body requirement // Each subsequent candle should open within previous candle's body if(i > 0 && rates[i].open > rates[i-1].open) return false; // Each candle should close lower than previous if(i > 0 && rates[i].close >= rates[i-1].close) return false; } return true; } //+------------------------------------------------------------------+ //| Mark pattern on chart with ONE rectangle covering entire pattern| //+------------------------------------------------------------------+ void MarkPatternWithRectangle(string patternName, int barIndex) { string objName = "PATTERN_" + IntegerToString(barIndex) + "_" + patternName; int candles = GetCandlesNeeded(patternName); // Find the highest high and lowest low in the pattern double highestHigh = -1; double lowestLow = -1; datetime startTime = 0; datetime endTime = 0; for(int i = 0; i < candles; i++) { double high = iHigh(_Symbol, _Period, barIndex + i); double low = iLow(_Symbol, _Period, barIndex + i); datetime candleTime = iTime(_Symbol, _Period, barIndex + i); if(i == 0) { highestHigh = high; lowestLow = low; startTime = candleTime; endTime = candleTime; } else { if(high > highestHigh) highestHigh = high; if(low < lowestLow) lowestLow = low; if(candleTime < startTime) startTime = candleTime; if(candleTime > endTime) endTime = candleTime; } } // Add some padding to make the rectangle more visible double padding = (highestHigh - lowestLow) * 0.1; highestHigh += padding; lowestLow -= padding; // Create ONE rectangle covering the entire pattern if(!ObjectCreate(0, objName, OBJ_RECTANGLE, 0, startTime, lowestLow, endTime, highestHigh)) return; ObjectSetInteger(0, objName, OBJPROP_COLOR, PatternColor); ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 7); ObjectSetInteger(0, objName, OBJPROP_FILL, false); ObjectSetInteger(0, objName, OBJPROP_BACK, true); } //+------------------------------------------------------------------+ //| Clear all patterns from chart | //+------------------------------------------------------------------+ void ClearPatterns() { ObjectsDeleteAll(0, "PATTERN_"); totalPatternsFound = 0; newPatternsDetected = 0; lastScanTime = 0; UpdateResultsDisplay(); Print("All patterns cleared"); } //+------------------------------------------------------------------+ //| Update results display | //+------------------------------------------------------------------+ void UpdateResultsDisplay() { string resultsText = StringFormat("📊 Total Patterns: %d\n⏰ Last Scan: %s", totalPatternsFound, (lastScanTime == 0) ? "Never" : TimeToString(lastScanTime, TIME_MINUTES)); string alertStatus = monitoringEnabled ? "🟢 ACTIVE" : "🔴 OFF"; string alertText = StringFormat("🔔 Monitoring: %s\nNew Patterns: %d", alertStatus, newPatternsDetected); ObjectSetString(0, "RESULTS_LABEL", OBJPROP_TEXT, resultsText); ObjectSetString(0, "ALERT_LABEL", OBJPROP_TEXT, alertText); } //+------------------------------------------------------------------+ //| GUI Creation Helper Functions | //+------------------------------------------------------------------+ int CreateRectangle(string name, int x, int y, int width, int height, color bgColor, color borderColor, bool back = false) { if(ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_XSIZE, width); ObjectSetInteger(0, name, OBJPROP_YSIZE, height); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor); ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, borderColor); ObjectSetInteger(0, name, OBJPROP_BACK, back); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); } return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CreateLabel(string name, int x, int y, string text, color textColor, int fontSize) { if(ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0)) { ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, textColor); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); } return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CreateEditBox(string name, int x, int y, int width, int height, string text = "") { if(ObjectCreate(0, name, OBJ_EDIT, 0, 0, 0)) { ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_XSIZE, width); ObjectSetInteger(0, name, OBJPROP_YSIZE, height); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_LEFT); } return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CreateButton(string name, int x, int y, int width, int height, string text, color bgColor) { if(ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0)) { ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_XSIZE, width); ObjectSetInteger(0, name, OBJPROP_YSIZE, height); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor); // Use predefined darker colors for borders instead of calculating color borderColor = GetDarkerBorderColor(bgColor); ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, borderColor); } return 0; } //+------------------------------------------------------------------+ //| Get predefined darker border colors | //+------------------------------------------------------------------+ color GetDarkerBorderColor(color bgColor) { if(bgColor == clrDodgerBlue) return clrBlue; if(bgColor == clrForestGreen) return clrGreen; if(bgColor == clrCrimson) return clrRed; if(bgColor == clrOrange) return clrDarkOrange; if(bgColor == clrPurple) return clrDarkOrchid; return clrGray; // Default fallback } //+------------------------------------------------------------------+ //| Convert string to lowercase (Fixed type conversion) | //+------------------------------------------------------------------+ string ConvertToLowercase(string str) { string result = ""; int len = StringLen(str); for(int i = 0; i < len; i++) { ushort ch = StringGetCharacter(str, i); // Convert uppercase to lowercase if(ch >= 65 && ch <= 90) // A-Z { ch += 32; // Convert to lowercase } // Use StringSetCharacter to avoid type conversion warnings StringSetCharacter(result, i, ch); } return result; }