This commit is contained in:
2026-07-10 04:24:32 +03:30
parent 76f57aff36
commit 369e6f0863
25 changed files with 1679 additions and 24 deletions
+360
View File
@@ -0,0 +1,360 @@
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
+3 -3
View File
@@ -103,7 +103,7 @@ ENUM_X_SESSION_OVERLAP_TYPE eaAllowedOverlaps[]; // Allowed Overlaps for Analysi
//
string eaTag; // EA Scoped Tag ...
bool isNewBar = false; // EA Is New Bar ...
bool isEaInNewBar = false; // EA Is New Bar ...
//
XCAlert *eaAlert; // EA Scope Alert Handler ...
@@ -172,7 +172,7 @@ void OnTick()
//
// Check if Is New Bar ...
isNewBar = eaBarTracker.IsNewBar();
isEaInNewBar = eaBarTracker.IsNewBar();
//
// Check Analysis Enabled ...
@@ -184,7 +184,7 @@ void OnTick()
{
//
// Check New Bar or not ...
if (!isNewBar)
if (!isEaInNewBar)
{
return;
}