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