//+------------------------------------------------------------------+ //| AbstractLiquidity.mqh | //| Copyright 2026, Concrete Goonies | //| https://concrete-goonies.web.app | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Concrete Goonies" #property link "https://concrete-goonies.web.app" #property version "1.00" #include "../AbstractAnalyser.mqh" struct EqConfigs { double tolerance; int minSwingCount; int lookBack; bool allowAverage; }; struct StructConfigs { double maxPercent; double minPercent; int lookBack; }; struct MomShiftConfigs { double maxPercent; double minPercent; double tolerance; // Dedicated tolerance for MomShift int maxSwingCount; // Dynamic swing limit for pullbacks }; struct LiquidityLvl { TimePrice a; TimePrice b; }; struct LiquidityConfigs { EqConfigs eqConfigs; StructConfigs structConfigs; MomShiftConfigs momShiftConfigs; }; struct ZigzagSwing { TimePrice point; bool isHigh; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class LiqState { public: string symbol; ENUM_TIMEFRAMES tf; CArrayList* m_swingHighs; CArrayList* m_swingLows; CArrayList* m_swings; // Pullback tracking (per state) double pullbackTargetPrice; double pullbackUpperLevel; // s3.y + tolerance double pullbackLowerLevel; // s1.y - tolerance int pullbackSwingCount; bool pullbackActive; datetime lastMomShiftS1Time; // prevents duplicate fires double momShiftInvalidationBuffer; // optional, or use configs // Inside LiqState: double protectedLowPrice; double protectedHighPrice; datetime protectedLowTime; datetime protectedHighTime; bool hasProtectedLow; bool hasProtectedHigh; TimePrice pullbackLastPoint; LiqState() { m_swingHighs = new CArrayList(); m_swingLows = new CArrayList(); m_swings = new CArrayList(); pullbackTargetPrice = 0.0; pullbackUpperLevel = 0.0; pullbackLowerLevel = 0.0; pullbackSwingCount = 0; pullbackActive = false; protectedLowPrice = 0.0; protectedHighPrice = 0.0; protectedLowTime = 0; protectedHighTime = 0; hasProtectedLow = false; hasProtectedHigh = false; } ~LiqState() { delete m_swingHighs; delete m_swingLows; delete m_swings; } bool HasSwingAt(datetime time, bool isHigh) { if(isHigh) { for(int i = 0; i < m_swingHighs.Size(); i++) if(m_swingHighs.At(i).x == time) return true; } else { for(int i = 0; i < m_swingLows.Size(); i++) if(m_swingLows.At(i).x == time) return true; } return false; } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class AbstractLiquidity : public AbstractAnalyser { protected: Indicator* ind; TimeFrameDataSeries* bar; LiqState* state; LiquidityConfigs configs; bool NearPrice(double a, double b, double tol) { return MathAbs(a - b) <= tol; } void CreateTrendL(string name, datetime t1, double p1, datetime t2, double p2, color clr) { long chart_id = ChartID(); bool created = ObjectCreate(chart_id, name, OBJ_TREND, 0, t1, p1, t2, p2); if(created) { ObjectSetInteger(chart_id, name, OBJPROP_COLOR, clr); ObjectSetInteger(chart_id, name, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(chart_id, name, OBJPROP_WIDTH, 2); } } void CreateTrendL(string name, const TimePrice &p1, const TimePrice &p2, color clr) { CreateTrendL(name, p1.x, p1.y, p2.x, p2.y, clr); } int CountSwingHighsBetween(datetime leftTime, datetime rightTime) { int count = 0; for(int i = 0; i < state.m_swingLows.Size(); i++) { TimePrice sl = state.m_swingLows.At(i); if(sl.x > leftTime && sl.x < rightTime) count++; } return count; } //--- Checks intermediate swings using absolute datetime (bypasses 5-bar limit) int CountSwingLowsBetween(datetime leftTime, datetime rightTime) { int count = 0; for(int i = 0; i < state.m_swingHighs.Size(); i++) { TimePrice sh = state.m_swingHighs.At(i); if(sh.x > leftTime && sh.x < rightTime) count++; } return count ; } bool HasSwingAt(datetime time, bool isHigh) { if(isHigh) { for(int i = 0; i < state.m_swingHighs.Size(); i++) { if(state.m_swingHighs.At(i).x == time) { return true; } } } else { for(int i = 0; i < state.m_swingLows.Size(); i++) { if(state.m_swingLows.At(i).x == time) return true; } } return false; } public: AbstractLiquidity() {} ~AbstractLiquidity() {} virtual void ProcessBar(TimeFrameDataSeries* series, int i) override { } virtual void ProcessBar(LiqState* s, Indicator* _ind, TimeFrameDataSeries* series, int i) { ind = _ind; bar = series; state = s; } void SetConfigs(LiquidityConfigs &c) { configs = c; } virtual void OnSwing(TimePrice &swing, bool isHigh) = 0; }; //+------------------------------------------------------------------+