mirror of
https://github.com/rithsila/MT5-EA-Sniper-Strategy.git
synced 2026-08-15 03:38:11 +00:00
- Add complete MT5 Expert Advisor with institutional trading concepts - Implement Order Blocks (OB), Break of Structure (BOS), Liquidity Sweeps, and Fair Value Gaps (FVG) - Include AI integration with GrokAI for enhanced market analysis - Add comprehensive risk management and session management systems - Implement advanced optimization and backtesting frameworks - Include complete test suite with integration, performance, and validation tests - Add professional documentation with API docs, deployment guide, and user manual - Update README.md with industry-standard documentation and Mermaid architecture diagram - Add comprehensive .gitignore for MT5 development environment - Include system validation and test results reports Features: ✅ Multi-timeframe analysis (1M, 15M, H4) ✅ Institutional trading concepts implementation ✅ AI-powered market structure analysis ✅ Advanced risk management with Monte Carlo simulation ✅ Real-time news filtering and fundamental analysis ✅ Adaptive parameter optimization ✅ Comprehensive testing and validation framework ✅ Professional documentation and deployment guides
697 lines
28 KiB
Plaintext
697 lines
28 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| LiquiditySweep.mqh |
|
|
//| Copyright 2024, MT5 Sniper Strategy Team |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2024, MT5 Sniper Strategy Team"
|
|
#property link "https://www.mql5.com"
|
|
|
|
#include "../Utils/Logger.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Liquidity Zone Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct SLiquidityZone {
|
|
datetime time; // Time of zone formation
|
|
double price; // Price level of liquidity
|
|
bool isHigh; // True for high liquidity, false for low
|
|
bool isSwept; // Has been swept
|
|
bool isValid; // Is the zone still valid
|
|
int strength; // Strength of liquidity (1-5)
|
|
double volume; // Volume at formation
|
|
int touchCount; // Number of times price touched this level
|
|
string timeframe; // Timeframe where zone was detected
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Liquidity Sweep Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct SLiquiditySweep {
|
|
datetime time; // Time of sweep
|
|
double sweepPrice; // Price where sweep occurred
|
|
double reversalPrice; // Price where reversal started
|
|
bool isBullishSweep; // True for bullish sweep (sweep lows then up)
|
|
bool isValid; // Is the sweep still valid
|
|
bool isConfirmed; // Has the sweep been confirmed with reversal
|
|
int strength; // Strength of the sweep (1-5)
|
|
double sweepDistance; // Distance of the sweep
|
|
string timeframe; // Timeframe where sweep was detected
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Liquidity Sweep Detector Class |
|
|
//+------------------------------------------------------------------+
|
|
class CLiquiditySweepDetector {
|
|
private:
|
|
string m_symbol;
|
|
ENUM_TIMEFRAMES m_timeframe;
|
|
CLogger* m_logger;
|
|
|
|
SLiquidityZone m_liquidityZones[];
|
|
SLiquiditySweep m_sweeps[];
|
|
int m_maxZones;
|
|
int m_maxSweeps;
|
|
|
|
// Detection parameters
|
|
int m_lookbackPeriod;
|
|
double m_minSweepDistance;
|
|
int m_reversalBars;
|
|
double m_liquidityThreshold;
|
|
bool m_useVolumeFilter;
|
|
double m_volumeMultiplier;
|
|
|
|
// Helper methods
|
|
bool DetectLiquidityZones();
|
|
bool IsLiquidityLevel(int index, bool checkHigh);
|
|
bool CheckForSweep();
|
|
bool IsBullishSweep(double sweepPrice, double currentPrice);
|
|
bool IsBearishSweep(double sweepPrice, double currentPrice);
|
|
int CalculateSweepStrength(const SLiquiditySweep &sweep);
|
|
bool ConfirmSweep(SLiquiditySweep &sweep);
|
|
void CleanupOldData();
|
|
SLiquidityZone GetNearestLiquidityZone(double price, bool isHigh);
|
|
|
|
public:
|
|
CLiquiditySweepDetector();
|
|
~CLiquiditySweepDetector();
|
|
|
|
bool Initialize(string symbol, ENUM_TIMEFRAMES timeframe, CLogger* logger);
|
|
void SetParameters(int lookback, double minSweepDistance, int reversalBars,
|
|
double liquidityThreshold, bool useVolume, double volumeMultiplier);
|
|
|
|
bool DetectSweeps();
|
|
int GetSweepCount();
|
|
SLiquiditySweep GetSweep(int index);
|
|
SLiquiditySweep GetLatestSweep(bool bullish);
|
|
|
|
bool IsRecentBullishSweep(int lookbackBars = 10);
|
|
bool IsRecentBearishSweep(int lookbackBars = 10);
|
|
bool HasValidSweep(bool checkBullish = true, bool checkBearish = true);
|
|
|
|
// Liquidity analysis
|
|
double GetNearestLiquidityHigh();
|
|
double GetNearestLiquidityLow();
|
|
bool IsLiquidityZone(double price, double tolerance = 0.0001);
|
|
int GetLiquidityZoneCount();
|
|
|
|
// Sweep validation
|
|
bool IsSweepAndReverse(bool bullish);
|
|
double GetSweepReversalLevel(bool bullish);
|
|
|
|
// Visualization
|
|
void DrawLiquidityZones();
|
|
void DrawSweeps();
|
|
void RemoveLiquidityObjects();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLiquiditySweepDetector::CLiquiditySweepDetector() {
|
|
m_symbol = "";
|
|
m_timeframe = PERIOD_CURRENT;
|
|
m_logger = NULL;
|
|
m_maxZones = 30;
|
|
m_maxSweeps = 20;
|
|
|
|
// Default parameters
|
|
m_lookbackPeriod = 20;
|
|
m_minSweepDistance = 0.0001;
|
|
m_reversalBars = 5;
|
|
m_liquidityThreshold = 0.0005;
|
|
m_useVolumeFilter = false;
|
|
m_volumeMultiplier = 1.5;
|
|
|
|
ArrayResize(m_liquidityZones, m_maxZones);
|
|
ArrayResize(m_sweeps, m_maxSweeps);
|
|
ArrayInitialize(m_liquidityZones, 0);
|
|
ArrayInitialize(m_sweeps, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CLiquiditySweepDetector::~CLiquiditySweepDetector() {
|
|
RemoveLiquidityObjects();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize detector |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::Initialize(string symbol, ENUM_TIMEFRAMES timeframe, CLogger* logger) {
|
|
m_symbol = symbol;
|
|
m_timeframe = timeframe;
|
|
m_logger = logger;
|
|
|
|
if(m_logger != NULL) {
|
|
m_logger->Info(StringFormat("Liquidity Sweep Detector initialized for %s on %s",
|
|
m_symbol, EnumToString(m_timeframe)));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set detection parameters |
|
|
//+------------------------------------------------------------------+
|
|
void CLiquiditySweepDetector::SetParameters(int lookback, double minSweepDistance, int reversalBars,
|
|
double liquidityThreshold, bool useVolume, double volumeMultiplier) {
|
|
m_lookbackPeriod = lookback;
|
|
m_minSweepDistance = minSweepDistance;
|
|
m_reversalBars = reversalBars;
|
|
m_liquidityThreshold = liquidityThreshold;
|
|
m_useVolumeFilter = useVolume;
|
|
m_volumeMultiplier = volumeMultiplier;
|
|
|
|
if(m_logger != NULL) {
|
|
m_logger->Debug(StringFormat("Liquidity Parameters: Lookback=%d, MinSweep=%.5f, Reversal=%d",
|
|
lookback, minSweepDistance, reversalBars));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detect liquidity sweeps |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::DetectSweeps() {
|
|
if(m_symbol == "" || m_timeframe == PERIOD_CURRENT) return false;
|
|
|
|
// First detect liquidity zones
|
|
if(!DetectLiquidityZones()) return false;
|
|
|
|
// Clean up old data
|
|
CleanupOldData();
|
|
|
|
// Check for new sweeps
|
|
return CheckForSweep();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detect liquidity zones |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::DetectLiquidityZones() {
|
|
int bars = iBars(m_symbol, m_timeframe);
|
|
if(bars < m_lookbackPeriod + 10) return false;
|
|
|
|
int zoneCount = 0;
|
|
|
|
// Clear existing zones
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
m_liquidityZones[i].isValid = false;
|
|
}
|
|
|
|
// Detect liquidity levels (equal highs/lows, support/resistance)
|
|
for(int i = 5; i < bars - 5 && zoneCount < m_maxZones; i++) {
|
|
// Check for liquidity high
|
|
if(IsLiquidityLevel(i, true)) {
|
|
m_liquidityZones[zoneCount].time = iTime(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].price = iHigh(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].isHigh = true;
|
|
m_liquidityZones[zoneCount].isSwept = false;
|
|
m_liquidityZones[zoneCount].isValid = true;
|
|
m_liquidityZones[zoneCount].volume = iVolume(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].touchCount = 1;
|
|
m_liquidityZones[zoneCount].timeframe = EnumToString(m_timeframe);
|
|
m_liquidityZones[zoneCount].strength = 1;
|
|
zoneCount++;
|
|
}
|
|
// Check for liquidity low
|
|
else if(IsLiquidityLevel(i, false)) {
|
|
m_liquidityZones[zoneCount].time = iTime(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].price = iLow(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].isHigh = false;
|
|
m_liquidityZones[zoneCount].isSwept = false;
|
|
m_liquidityZones[zoneCount].isValid = true;
|
|
m_liquidityZones[zoneCount].volume = iVolume(m_symbol, m_timeframe, i);
|
|
m_liquidityZones[zoneCount].touchCount = 1;
|
|
m_liquidityZones[zoneCount].timeframe = EnumToString(m_timeframe);
|
|
m_liquidityZones[zoneCount].strength = 1;
|
|
zoneCount++;
|
|
}
|
|
}
|
|
|
|
// Calculate strength and touch count for each zone
|
|
for(int i = 0; i < zoneCount; i++) {
|
|
if(!m_liquidityZones[i].isValid) continue;
|
|
|
|
int touches = 0;
|
|
double zonePrice = m_liquidityZones[i].price;
|
|
bool isHigh = m_liquidityZones[i].isHigh;
|
|
|
|
// Count how many times price touched this level
|
|
for(int j = 0; j < bars - 1; j++) {
|
|
double high = iHigh(m_symbol, m_timeframe, j);
|
|
double low = iLow(m_symbol, m_timeframe, j);
|
|
|
|
if(isHigh) {
|
|
if(MathAbs(high - zonePrice) <= m_liquidityThreshold) touches++;
|
|
} else {
|
|
if(MathAbs(low - zonePrice) <= m_liquidityThreshold) touches++;
|
|
}
|
|
}
|
|
|
|
m_liquidityZones[i].touchCount = touches;
|
|
m_liquidityZones[i].strength = MathMin(touches, 5);
|
|
}
|
|
|
|
if(m_logger != NULL) {
|
|
m_logger->Debug(StringFormat("Detected %d liquidity zones", zoneCount));
|
|
}
|
|
|
|
return zoneCount > 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if level is a liquidity level |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::IsLiquidityLevel(int index, bool checkHigh) {
|
|
if(index <= 2 || index >= iBars(m_symbol, m_timeframe) - 2) return false;
|
|
|
|
double currentPrice = checkHigh ? iHigh(m_symbol, m_timeframe, index) : iLow(m_symbol, m_timeframe, index);
|
|
int matches = 0;
|
|
|
|
// Look for equal highs/lows within the lookback period
|
|
for(int i = index - m_lookbackPeriod; i <= index + m_lookbackPeriod; i++) {
|
|
if(i == index || i < 0 || i >= iBars(m_symbol, m_timeframe)) continue;
|
|
|
|
double comparePrice = checkHigh ? iHigh(m_symbol, m_timeframe, i) : iLow(m_symbol, m_timeframe, i);
|
|
|
|
if(MathAbs(currentPrice - comparePrice) <= m_liquidityThreshold) {
|
|
matches++;
|
|
}
|
|
}
|
|
|
|
// Need at least 2 matches to be considered liquidity
|
|
return matches >= 2;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for liquidity sweep |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::CheckForSweep() {
|
|
double currentPrice = iClose(m_symbol, m_timeframe, 0);
|
|
bool foundSweep = false;
|
|
|
|
// Check each liquidity zone for potential sweep
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(!m_liquidityZones[i].isValid || m_liquidityZones[i].isSwept) continue;
|
|
|
|
double zonePrice = m_liquidityZones[i].price;
|
|
bool isHigh = m_liquidityZones[i].isHigh;
|
|
|
|
// Check if price has swept through the liquidity zone
|
|
bool swept = false;
|
|
if(isHigh) {
|
|
// For high liquidity, check if price went above and then reversed
|
|
if(currentPrice > zonePrice + m_minSweepDistance) {
|
|
// Check for reversal
|
|
bool hasReversal = false;
|
|
for(int j = 1; j <= m_reversalBars; j++) {
|
|
double pastPrice = iClose(m_symbol, m_timeframe, j);
|
|
if(pastPrice < zonePrice) {
|
|
hasReversal = true;
|
|
break;
|
|
}
|
|
}
|
|
swept = hasReversal;
|
|
}
|
|
} else {
|
|
// For low liquidity, check if price went below and then reversed
|
|
if(currentPrice < zonePrice - m_minSweepDistance) {
|
|
// Check for reversal
|
|
bool hasReversal = false;
|
|
for(int j = 1; j <= m_reversalBars; j++) {
|
|
double pastPrice = iClose(m_symbol, m_timeframe, j);
|
|
if(pastPrice > zonePrice) {
|
|
hasReversal = true;
|
|
break;
|
|
}
|
|
}
|
|
swept = hasReversal;
|
|
}
|
|
}
|
|
|
|
if(swept) {
|
|
// Mark zone as swept
|
|
m_liquidityZones[i].isSwept = true;
|
|
|
|
// Create sweep signal
|
|
SLiquiditySweep newSweep;
|
|
newSweep.time = TimeCurrent();
|
|
newSweep.sweepPrice = zonePrice;
|
|
newSweep.reversalPrice = currentPrice;
|
|
newSweep.isBullishSweep = !isHigh; // Sweep lows = bullish, sweep highs = bearish
|
|
newSweep.isValid = true;
|
|
newSweep.isConfirmed = false;
|
|
newSweep.sweepDistance = MathAbs(currentPrice - zonePrice);
|
|
newSweep.timeframe = EnumToString(m_timeframe);
|
|
newSweep.strength = CalculateSweepStrength(newSweep);
|
|
|
|
// Add to array
|
|
for(int j = 0; j < ArraySize(m_sweeps); j++) {
|
|
if(!m_sweeps[j].isValid) {
|
|
m_sweeps[j] = newSweep;
|
|
foundSweep = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(foundSweep && m_logger != NULL) {
|
|
m_logger->LogMarketStructure(
|
|
StringFormat("%s Liquidity Sweep", newSweep.isBullishSweep ? "Bullish" : "Bearish"),
|
|
m_symbol, newSweep.sweepPrice, newSweep.time
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return foundSweep;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate sweep strength |
|
|
//+------------------------------------------------------------------+
|
|
int CLiquiditySweepDetector::CalculateSweepStrength(const SLiquiditySweep &sweep) {
|
|
int strength = 1;
|
|
|
|
// Distance of sweep
|
|
double atr = iATR(m_symbol, m_timeframe, 14, 1);
|
|
if(atr > 0) {
|
|
double sweepRatio = sweep.sweepDistance / atr;
|
|
if(sweepRatio > 0.5) strength++;
|
|
if(sweepRatio > 1.0) strength++;
|
|
}
|
|
|
|
// Volume confirmation
|
|
if(m_useVolumeFilter) {
|
|
double currentVolume = iVolume(m_symbol, m_timeframe, 0);
|
|
double avgVolume = 0;
|
|
for(int i = 1; i <= 10; i++) {
|
|
avgVolume += iVolume(m_symbol, m_timeframe, i);
|
|
}
|
|
avgVolume /= 10;
|
|
|
|
if(currentVolume > avgVolume * m_volumeMultiplier) strength++;
|
|
}
|
|
|
|
// Speed of reversal
|
|
int reversalSpeed = 0;
|
|
double startPrice = sweep.sweepPrice;
|
|
double endPrice = sweep.reversalPrice;
|
|
|
|
for(int i = 1; i <= 5; i++) {
|
|
double price = iClose(m_symbol, m_timeframe, i);
|
|
if(sweep.isBullishSweep) {
|
|
if(price > startPrice) {
|
|
reversalSpeed = 6 - i; // Faster reversal = higher score
|
|
break;
|
|
}
|
|
} else {
|
|
if(price < startPrice) {
|
|
reversalSpeed = 6 - i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(reversalSpeed >= 4) strength++;
|
|
|
|
return MathMin(strength, 5);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get nearest liquidity zone |
|
|
//+------------------------------------------------------------------+
|
|
SLiquidityZone CLiquiditySweepDetector::GetNearestLiquidityZone(double price, bool isHigh) {
|
|
SLiquidityZone nearestZone = {0};
|
|
double nearestDistance = DBL_MAX;
|
|
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(!m_liquidityZones[i].isValid || m_liquidityZones[i].isSwept) continue;
|
|
if(m_liquidityZones[i].isHigh != isHigh) continue;
|
|
|
|
double distance = MathAbs(price - m_liquidityZones[i].price);
|
|
if(distance < nearestDistance) {
|
|
nearestDistance = distance;
|
|
nearestZone = m_liquidityZones[i];
|
|
}
|
|
}
|
|
|
|
return nearestZone;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clean up old data |
|
|
//+------------------------------------------------------------------+
|
|
void CLiquiditySweepDetector::CleanupOldData() {
|
|
datetime currentTime = TimeCurrent();
|
|
|
|
// Clean up old liquidity zones
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(m_liquidityZones[i].isValid) {
|
|
if(currentTime - m_liquidityZones[i].time > PeriodSeconds(m_timeframe) * 100) {
|
|
m_liquidityZones[i].isValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up old sweeps
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(m_sweeps[i].isValid) {
|
|
if(currentTime - m_sweeps[i].time > PeriodSeconds(m_timeframe) * 50) {
|
|
m_sweeps[i].isValid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get sweep count |
|
|
//+------------------------------------------------------------------+
|
|
int CLiquiditySweepDetector::GetSweepCount() {
|
|
int count = 0;
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(m_sweeps[i].isValid) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get sweep by index |
|
|
//+------------------------------------------------------------------+
|
|
SLiquiditySweep CLiquiditySweepDetector::GetSweep(int index) {
|
|
SLiquiditySweep emptySweep = {0};
|
|
|
|
if(index < 0 || index >= ArraySize(m_sweeps)) return emptySweep;
|
|
if(!m_sweeps[index].isValid) return emptySweep;
|
|
|
|
return m_sweeps[index];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get latest sweep |
|
|
//+------------------------------------------------------------------+
|
|
SLiquiditySweep CLiquiditySweepDetector::GetLatestSweep(bool bullish) {
|
|
SLiquiditySweep latestSweep = {0};
|
|
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(m_sweeps[i].isValid && m_sweeps[i].isBullishSweep == bullish) {
|
|
if(latestSweep.time == 0 || m_sweeps[i].time > latestSweep.time) {
|
|
latestSweep = m_sweeps[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return latestSweep;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for recent bullish sweep |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::IsRecentBullishSweep(int lookbackBars = 10) {
|
|
datetime cutoffTime = TimeCurrent() - PeriodSeconds(m_timeframe) * lookbackBars;
|
|
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(m_sweeps[i].isValid && m_sweeps[i].isBullishSweep &&
|
|
m_sweeps[i].time >= cutoffTime) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for recent bearish sweep |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::IsRecentBearishSweep(int lookbackBars = 10) {
|
|
datetime cutoffTime = TimeCurrent() - PeriodSeconds(m_timeframe) * lookbackBars;
|
|
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(m_sweeps[i].isValid && !m_sweeps[i].isBullishSweep &&
|
|
m_sweeps[i].time >= cutoffTime) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if has valid sweep |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::HasValidSweep(bool checkBullish = true, bool checkBearish = true) {
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(!m_sweeps[i].isValid) continue;
|
|
|
|
if(m_sweeps[i].isBullishSweep && checkBullish) return true;
|
|
if(!m_sweeps[i].isBullishSweep && checkBearish) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get nearest liquidity high |
|
|
//+------------------------------------------------------------------+
|
|
double CLiquiditySweepDetector::GetNearestLiquidityHigh() {
|
|
double currentPrice = iClose(m_symbol, m_timeframe, 0);
|
|
SLiquidityZone nearestHigh = GetNearestLiquidityZone(currentPrice, true);
|
|
|
|
return nearestHigh.isValid ? nearestHigh.price : 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get nearest liquidity low |
|
|
//+------------------------------------------------------------------+
|
|
double CLiquiditySweepDetector::GetNearestLiquidityLow() {
|
|
double currentPrice = iClose(m_symbol, m_timeframe, 0);
|
|
SLiquidityZone nearestLow = GetNearestLiquidityZone(currentPrice, false);
|
|
|
|
return nearestLow.isValid ? nearestLow.price : 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if price is in liquidity zone |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::IsLiquidityZone(double price, double tolerance = 0.0001) {
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(!m_liquidityZones[i].isValid || m_liquidityZones[i].isSwept) continue;
|
|
|
|
if(MathAbs(price - m_liquidityZones[i].price) <= tolerance) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get liquidity zone count |
|
|
//+------------------------------------------------------------------+
|
|
int CLiquiditySweepDetector::GetLiquidityZoneCount() {
|
|
int count = 0;
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(m_liquidityZones[i].isValid && !m_liquidityZones[i].isSwept) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if sweep and reverse pattern |
|
|
//+------------------------------------------------------------------+
|
|
bool CLiquiditySweepDetector::IsSweepAndReverse(bool bullish) {
|
|
SLiquiditySweep latestSweep = GetLatestSweep(bullish);
|
|
|
|
if(!latestSweep.isValid) return false;
|
|
|
|
// Check if the sweep happened recently (within last 10 bars)
|
|
datetime cutoffTime = TimeCurrent() - PeriodSeconds(m_timeframe) * 10;
|
|
if(latestSweep.time < cutoffTime) return false;
|
|
|
|
// Check if price is moving in the expected direction after sweep
|
|
double currentPrice = iClose(m_symbol, m_timeframe, 0);
|
|
|
|
if(bullish) {
|
|
return currentPrice > latestSweep.sweepPrice;
|
|
} else {
|
|
return currentPrice < latestSweep.sweepPrice;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get sweep reversal level |
|
|
//+------------------------------------------------------------------+
|
|
double CLiquiditySweepDetector::GetSweepReversalLevel(bool bullish) {
|
|
SLiquiditySweep latestSweep = GetLatestSweep(bullish);
|
|
|
|
return latestSweep.isValid ? latestSweep.reversalPrice : 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw liquidity zones |
|
|
//+------------------------------------------------------------------+
|
|
void CLiquiditySweepDetector::DrawLiquidityZones() {
|
|
for(int i = 0; i < ArraySize(m_liquidityZones); i++) {
|
|
if(!m_liquidityZones[i].isValid) continue;
|
|
|
|
string objName = StringFormat("LIQ_%s_%d", m_symbol, i);
|
|
color zoneColor = m_liquidityZones[i].isSwept ? clrGray :
|
|
(m_liquidityZones[i].isHigh ? clrRed : clrBlue);
|
|
|
|
// Create horizontal line
|
|
if(ObjectCreate(0, objName, OBJ_HLINE, 0, 0, m_liquidityZones[i].price)) {
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
|
|
ObjectSetInteger(0, objName, OBJPROP_STYLE, m_liquidityZones[i].isSwept ? STYLE_DOT : STYLE_DASH);
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
|
|
ObjectSetString(0, objName, OBJPROP_TOOLTIP,
|
|
StringFormat("Liquidity %s (Strength: %d, Touches: %d)",
|
|
m_liquidityZones[i].isHigh ? "High" : "Low",
|
|
m_liquidityZones[i].strength,
|
|
m_liquidityZones[i].touchCount));
|
|
}
|
|
}
|
|
|
|
ChartRedraw();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw sweeps |
|
|
//+------------------------------------------------------------------+
|
|
void CLiquiditySweepDetector::DrawSweeps() {
|
|
for(int i = 0; i < ArraySize(m_sweeps); i++) {
|
|
if(!m_sweeps[i].isValid) continue;
|
|
|
|
string objName = StringFormat("SWEEP_%s_%d", m_symbol, i);
|
|
color sweepColor = m_sweeps[i].isBullishSweep ? clrLime : clrRed;
|
|
|
|
// Create arrow object
|
|
if(ObjectCreate(0, objName, OBJ_ARROW, 0, m_sweeps[i].time, m_sweeps[i].sweepPrice)) {
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, sweepColor);
|
|
ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, m_sweeps[i].isBullishSweep ? 241 : 242);
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
|
|
ObjectSetString(0, objName, OBJPROP_TOOLTIP,
|
|
StringFormat("%s Liquidity Sweep (Strength: %d)",
|
|
m_sweeps[i].isBullishSweep ? "Bullish" : "Bearish",
|
|
m_sweeps[i].strength));
|
|
}
|
|
}
|
|
|
|
ChartRedraw();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Remove liquidity objects |
|
|
//+------------------------------------------------------------------+
|
|
void CLiquiditySweepDetector::RemoveLiquidityObjects() {
|
|
string liqPrefix = StringFormat("LIQ_%s_", m_symbol);
|
|
string sweepPrefix = StringFormat("SWEEP_%s_", m_symbol);
|
|
|
|
for(int i = ObjectsTotal(0) - 1; i >= 0; i--) {
|
|
string objName = ObjectName(0, i);
|
|
if(StringFind(objName, liqPrefix) == 0 || StringFind(objName, sweepPrefix) == 0) {
|
|
ObjectDelete(0, objName);
|
|
}
|
|
}
|
|
|
|
ChartRedraw();
|
|
} |