Files
MT5-EA-Sniper-Strategy/src/Include/MarketStructure/LiquiditySweep.mqh
T
sila b6166d4246 feat: Complete MT5 EA Sniper Strategy implementation with comprehensive documentation
- 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
2025-09-20 15:25:18 +07:00

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();
}