//+------------------------------------------------------------------+ //| SmartShadowScanner.mq5 | //| Advanced Shadow Scanner with 3-Step Checklist Filter | //+------------------------------------------------------------------+ #property copyright "Professional Trader Setup 2026" #property version "3.00" #property description "Scans for large rejection shadows filtered by Sweep, POI, and Session" //--- User Inputs input group "=== Base Shadow Settings ===" input double ShadowMultiplier = 2.0; // Shadow to Body Multiplier (e.g., 2.0 means shadow is 2x body) input double MinRangeATR_Multi = 0.5; // Min Candle Range as ATR Multiplier (0.5 = 50% of ATR) input int MinCandleSizePoints = 30; // Min Absolute Candle Size in Points (Filters micro-candles) input ENUM_TIMEFRAMES Timeframe = PERIOD_H4; // Timeframe to Analyze input group "=== Filter 1: Liquidity Sweep ===" input bool EnableSweepFilter = true; // Enable Liquidity Sweep Filter input int SwingLookback = 10; // Swing Lookback Period (Left & Right candles) input group "=== Filter 2: Point of Interest (POI) ===" input bool EnablePOIFilter = true; // Enable POI Filter input int POI_ProximityPoints = 50; // Allowed Distance to POI in Points input bool CheckPDH_PDL = true; // Check Previous Day High/Low input bool CheckOrderBlock = true; // Check Recent Order Blocks input group "=== Filter 3: Trading Session ===" input bool EnableSessionFilter = true; // Enable Session Time Filter input int LondonStartHour = 8; // London Start Hour (Server Time) input int LondonEndHour = 12; // London End Hour (Server Time) input int NYStartHour = 13; // New York Start Hour (Server Time) input int NYEndHour = 18; // New York End Hour (Server Time) input group "=== Display & Alert Settings ===" input bool DrawArrows = true; // Draw Arrows on Chart input bool ShowAlerts = true; // Show Popup Alerts input bool SendPush = false; // Send Push Notifications input color BullishArrowColor = clrLime; // Bullish Arrow Color (Lower Shadow) input color BearishArrowColor = clrRed; // Bearish Arrow Color (Upper Shadow) input int ArrowDistancePoints = 10; // Arrow Distance from Shadow Tip (Points) //--- Global Variables datetime lastBarTime = 0; int atrHandle = INVALID_HANDLE; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize ATR indicator handle for volatility filtering atrHandle = iATR(_Symbol, Timeframe, 14); if(atrHandle == INVALID_HANDLE) { Print("Error creating ATR indicator handle!"); return(INIT_FAILED); } Print("=== Smart Shadow Scanner v3.0 Initialized ==="); Print("Sweep Filter: ", EnableSweepFilter ? "Enabled" : "Disabled"); Print("POI Filter: ", EnablePOIFilter ? "Enabled" : "Disabled"); Print("Session Filter: ", EnableSessionFilter ? "Enabled" : "Disabled"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up chart objects and release indicator memory ObjectsDeleteAll(0, "ShadowArrow_"); IndicatorRelease(atrHandle); ChartRedraw(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Process only on new bar close to avoid redundant calculations datetime currentBarTime = iTime(_Symbol, Timeframe, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Copy required bars (Need enough history for Swing Lookback) int barsNeeded = SwingLookback * 2 + 10; MqlRates rates[]; ArraySetAsSeries(rates, true); if(CopyRates(_Symbol, Timeframe, 0, barsNeeded, rates) < barsNeeded) return; // Copy ATR value for the closed candle (index 1) double atrBuffer[]; ArraySetAsSeries(atrBuffer, true); if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) < 1) return; double currentATR = atrBuffer[0]; // Extract OHLC data for the target candle (index 1) double openPrice = rates[1].open; double highPrice = rates[1].high; double lowPrice = rates[1].low; double closePrice = rates[1].close; datetime barTime = rates[1].time; // Calculate candle metrics double body = MathAbs(openPrice - closePrice); double upperShadow = highPrice - MathMax(openPrice, closePrice); double lowerShadow = MathMin(openPrice, closePrice) - lowPrice; double totalRange = highPrice - lowPrice; // ========================================== // Base Filters (Volatility & Size) // ========================================== // Filter 1: Absolute minimum size (ignores dead-market micro candles) if(totalRange < MinCandleSizePoints * _Point) return; // Filter 2: Dynamic ATR filter (candle must be at least X% of average range) if(totalRange < (currentATR * MinRangeATR_Multi)) return; // Identify shadow type bool isBullishShadow = (lowerShadow >= (ShadowMultiplier * body) && lowerShadow > upperShadow); bool isBearishShadow = (upperShadow >= (ShadowMultiplier * body) && upperShadow > lowerShadow); if(!isBullishShadow && !isBearishShadow) return; // ========================================== // Advanced 3-Step Checklist // ========================================== bool passedSweep = !EnableSweepFilter || CheckLiquiditySweep(rates, isBullishShadow); bool passedPOI = !EnablePOIFilter || CheckNearPOI(rates, isBullishShadow, barTime); bool passedSession = !EnableSessionFilter || CheckSession(barTime); // Trigger final signal only if ALL enabled filters are passed if(passedSweep && passedPOI && passedSession) { ProcessSignal(isBullishShadow, barTime, isBullishShadow ? lowPrice : highPrice); } } //+------------------------------------------------------------------+ //| Filter 1: Check for Liquidity Sweep | //+------------------------------------------------------------------+ bool CheckLiquiditySweep(const MqlRates &rates[], bool isBullish) { int targetIndex = 1; // Target candle is at index 1 if(isBullish) // For lower shadow, it must have swept a previous Swing Low { for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++) { if(IsSwingLow(rates, i, SwingLookback)) { double swingLow = rates[i].low; // Sweep condition: Wick pierces the swing low, but body closes above it if(rates[targetIndex].low < swingLow && rates[targetIndex].close > swingLow) { return true; // Valid liquidity sweep detected } } } } else // For upper shadow, it must have swept a previous Swing High { for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++) { if(IsSwingHigh(rates, i, SwingLookback)) { double swingHigh = rates[i].high; // Sweep condition: Wick pierces the swing high, but body closes below it if(rates[targetIndex].high > swingHigh && rates[targetIndex].close < swingHigh) { return true; // Valid liquidity sweep detected } } } } return false; // No valid sweep found } //+------------------------------------------------------------------+ //| Identify Swing High | //+------------------------------------------------------------------+ bool IsSwingHigh(const MqlRates &rates[], int index, int lookback) { if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false; double high = rates[index].high; for(int i = 1; i <= lookback; i++) { if(rates[index - i].high >= high) return false; if(rates[index + i].high >= high) return false; } return true; } //+------------------------------------------------------------------+ //| Identify Swing Low | //+------------------------------------------------------------------+ bool IsSwingLow(const MqlRates &rates[], int index, int lookback) { if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false; double low = rates[index].low; for(int i = 1; i <= lookback; i++) { if(rates[index - i].low <= low) return false; if(rates[index + i].low <= low) return false; } return true; } //+------------------------------------------------------------------+ //| Filter 2: Check Proximity to Point of Interest (POI) | //+------------------------------------------------------------------+ bool CheckNearPOI(const MqlRates &rates[], bool isBullish, datetime barTime) { double currentPrice = isBullish ? rates[1].low : rates[1].high; double proximity = POI_ProximityPoints * _Point; // POI Type 1: Previous Day High / Previous Day Low (PDH/PDL) if(CheckPDH_PDL) { MqlRates dayRates[]; ArraySetAsSeries(dayRates, true); // Fetch previous daily candle data if(CopyRates(_Symbol, PERIOD_D1, 1, 1, dayRates) >= 1) { double pdh = dayRates[0].high; double pdl = dayRates[0].low; if(isBullish && MathAbs(currentPrice - pdl) <= proximity) return true; // Near PDL if(!isBullish && MathAbs(currentPrice - pdh) <= proximity) return true; // Near PDH } } // POI Type 2: Recent Order Blocks (OB) if(CheckOrderBlock) { // Scan the last 20 candles for Order Blocks for(int i = 2; i < MathMin(20, ArraySize(rates) - 1); i++) { // Bullish OB: Bearish candle followed by a strong bullish displacement if(isBullish && IsBullishOrderBlock(rates, i)) { double obLow = rates[i].low; double obHigh = rates[i].high; if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity)) return true; } // Bearish OB: Bullish candle followed by a strong bearish displacement if(!isBullish && IsBearishOrderBlock(rates, i)) { double obLow = rates[i].low; double obHigh = rates[i].high; if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity)) return true; } } } return false; // Not near any valid POI } //+------------------------------------------------------------------+ //| Identify Bullish Order Block | //+------------------------------------------------------------------+ bool IsBullishOrderBlock(const MqlRates &rates[], int index) { // Current candle must be bearish if(rates[index].close >= rates[index].open) return false; if(index - 1 < 0) return false; // Next candle (index-1) must be a strong bullish displacement double nextBody = MathAbs(rates[index-1].close - rates[index-1].open); double nextRange = rates[index-1].high - rates[index-1].low; if(rates[index-1].close <= rates[index-1].open) return false; // Must be bullish if(nextBody < (nextRange * 0.6)) return false; // Body must be at least 60% of total range return true; } //+------------------------------------------------------------------+ //| Identify Bearish Order Block | //+------------------------------------------------------------------+ bool IsBearishOrderBlock(const MqlRates &rates[], int index) { // Current candle must be bullish if(rates[index].close <= rates[index].open) return false; if(index - 1 < 0) return false; // Next candle (index-1) must be a strong bearish displacement double nextBody = MathAbs(rates[index-1].close - rates[index-1].open); double nextRange = rates[index-1].high - rates[index-1].low; if(rates[index-1].close >= rates[index-1].open) return false; // Must be bearish if(nextBody < (nextRange * 0.6)) return false; return true; } //+------------------------------------------------------------------+ //| Filter 3: Check Trading Session Time | //+------------------------------------------------------------------+ bool CheckSession(datetime barTime) { MqlDateTime dt; TimeToStruct(barTime, dt); int hour = dt.hour; // London Session if(hour >= LondonStartHour && hour < LondonEndHour) return true; // New York Session if(hour >= NYStartHour && hour < NYEndHour) return true; return false; // Outside active sessions (e.g., Asian session) } //+------------------------------------------------------------------+ //| Process Final Signal (Draw, Alert, Push) | //+------------------------------------------------------------------+ void ProcessSignal(bool isBullish, datetime barTime, double extremePrice) { string direction = isBullish ? "BULLISH" : "BEARISH"; string message = StringFormat("✅ Golden %s Signal | %s | Price: %s", direction, TimeToString(barTime, TIME_DATE|TIME_MINUTES), DoubleToString(extremePrice, _Digits)); // 1. Draw Arrow on Chart if(DrawArrows) { string objName = "ShadowArrow_" + IntegerToString(barTime); int arrowCode = isBullish ? 233 : 234; // 233: Up arrow, 234: Down arrow color arrowColor = isBullish ? BullishArrowColor : BearishArrowColor; double arrowPrice = isBullish ? (extremePrice - ArrowDistancePoints * _Point) : (extremePrice + ArrowDistancePoints * _Point); ObjectCreate(0, objName, OBJ_ARROW, 0, barTime, arrowPrice); ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrowCode); ObjectSetInteger(0, objName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3); ObjectSetInteger(0, objName, OBJPROP_ANCHOR, isBullish ? ANCHOR_TOP : ANCHOR_BOTTOM); } // 2. Trigger Alerts if(ShowAlerts) Alert(message); if(SendPush) SendNotification(message); // 3. Print to Experts Log Print(message); } //+------------------------------------------------------------------+