//+------------------------------------------------------------------+ //| C_Chapter3_Liquidity.mqh | //| Chapter 3 wired to your Chapter 2 exports | //+------------------------------------------------------------------+ #property strict #property version "1.00" #include "AbstractChapter.mqh" //======================== // Inputs //======================== input int ch3_eqTolerancePts = 25; input int ch3_minPoolSeparationBars = 3; input int ch3_imbLookbackBars = 300; input int ch3_maxPoolsPerType = 25; //======================== // Enums //======================== enum EImbalanceSide { IMB_BULL = 0, IMB_BEAR = 1 }; enum EImbalanceState { IMB_OPEN = 0, IMB_MITIGATED = 1 }; enum ELiquiditySide { LQ_BSL = 0, LQ_SSL = 1 }; enum ELiquidityKind { LQ_EQUALS = 0, LQ_STRUCTURE = 1, LQ_EXTERNAL = 2 }; enum ELiquidityState { LQ_ACTIVE = 0, LQ_SWEPT = 1 }; struct ImbalanceZone { int id; EImbalanceSide side; EImbalanceState state; int leftBar; int rightBar; double top; double bottom; int mitigateBar; }; struct LiquidityPool { int id; ELiquiditySide side; ELiquidityKind kind; ELiquidityState state; int leftBar; int rightBar; double price; int sweepBar; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class C_Chapter3 : public CAbstractChapter { private: FairValue fairValue; ImbalanceZone imbalances[]; LiquidityPool pools[]; int nextImbId; int nextPoolId; // ERL anchors from Chapter 2 int protectedHigh; int protectedLow; private: //======================== // Keys //======================== string K_IMB(int id) { return "CH3_IMB_" + (string)id; } string K_POOL(int id) { return "CH3_POOL_" + (string)id; } string K_ERL_HIGH() { return "CH3_ERL_HIGH"; } string K_ERL_LOW() { return "CH3_ERL_LOW"; } //======================== // Helpers //======================== double TolerancePrice() { return ch3_eqTolerancePts * _Point; } bool IsValidBar(int index) { return (index >= 0 && index < bar.Size()); } int MinInt(int a, int b) { return (a < b ? a : b); } int MaxInt(int a, int b) { return (a > b ? a : b); } bool NearPrice(double a, double b, double tol) { return MathAbs(a - b) <= tol; } void DrawLevel(string key,int fromIndex,int toIndex,double price,string text,color col,int style=1) { if(!IsValidBar(fromIndex) || !IsValidBar(toIndex)) return; debug.LineWithCenteredText( key, bar.Time(fromIndex), price, bar.Time(toIndex), price, text, style, col ); } void PushImb(ImbalanceZone &z) { int n = ArraySize(imbalances); ArrayResize(imbalances, n + 1); imbalances[n] = z; } void PushPool(LiquidityPool &p) { int n = ArraySize(pools); ArrayResize(pools, n + 1); pools[n] = p; } bool PoolExists(ELiquiditySide side, ELiquidityKind kind, int leftBar, int rightBar, double price) { double tol = TolerancePrice(); for(int i = 0; i < ArraySize(pools); i++) { if(pools[i].side != side) continue; if(pools[i].kind != kind) continue; if(pools[i].leftBar != leftBar) continue; if(pools[i].rightBar != rightBar) continue; if(NearPrice(pools[i].price, price, tol)) return true; } return false; } bool ImbalanceExists(EImbalanceSide side, int leftBar, int rightBar, double top, double bottom) { double tol = TolerancePrice(); for(int i = 0; i < ArraySize(imbalances); i++) { if(imbalances[i].side != side) continue; if(imbalances[i].leftBar != leftBar) continue; if(imbalances[i].rightBar != rightBar) continue; if(NearPrice(imbalances[i].top, top, tol) && NearPrice(imbalances[i].bottom, bottom, tol)) return true; } return false; } int CountPoolsOfSide(ELiquiditySide side) { int count = 0; for(int i = 0; i < ArraySize(pools); i++) { if(pools[i].side == side) count++; } return count; } //======================== // Imbalances //======================== void DetectImbalances() { int end = bar.Size() - 1; if(end < 3) return; int start = end - ch3_imbLookbackBars; if(start < 2) start = 2; for(int i = start; i <= end; i++) { Fvg fvg; int l = i - 1; fairValue.getBullFvg(fvg, i - 1); if(fairValue.IsValidValue(fvg)) { if(!ImbalanceExists(IMB_BULL, l, i, fvg.top, fvg.bottom)) { ImbalanceZone z; z.id = nextImbId++; z.side = IMB_BULL; z.state = IMB_OPEN; z.leftBar = l; z.rightBar = i; z.top = fvg.top; z.bottom = fvg.bottom; z.mitigateBar = -1; PushImb(z); } } fairValue.getBearFvg(fvg, i - 1); if(fairValue.IsValidValue(fvg)) { if(!ImbalanceExists(IMB_BEAR, l, i, fvg.top, fvg.bottom)) { ImbalanceZone z; z.id = nextImbId++; z.side = IMB_BEAR; z.state = IMB_OPEN; z.leftBar = l; z.rightBar = i; z.top = fvg.top; z.bottom = fvg.bottom; z.mitigateBar = -1; PushImb(z); } } } } void UpdateImbalanceMitigation() { int end = bar.Size() - 1; for(int k = 0; k < ArraySize(imbalances); k++) { if(imbalances[k].state == IMB_MITIGATED) continue; for(int i = imbalances[k].rightBar; i <= end; i++) { bool touched = false; if(imbalances[k].side == IMB_BULL) { if(bar.Low(i) <= imbalances[k].top && bar.High(i) >= imbalances[k].bottom) touched = true; } else { if(bar.High(i) >= imbalances[k].bottom && bar.Low(i) <= imbalances[k].top) touched = true; } if(touched) { imbalances[k].state = IMB_MITIGATED; imbalances[k].mitigateBar = i; break; } } } } void DrawImbalances() { int end = bar.Size() - 1; for(int i = 0; i < ArraySize(imbalances); i++) { ImbalanceZone z = imbalances[i]; string txt = (z.side == IMB_BULL ? "IMB BULL" : "IMB BEAR"); if(z.state == IMB_MITIGATED) txt += " MITIGATED"; int toIndex = (z.mitigateBar >= 0 ? z.mitigateBar : end); color col = clrSilver; if(z.state != IMB_MITIGATED) col = (z.side == IMB_BULL ? clrPaleGreen : clrTomato); debug.RectWithCenteredText( symbol, K_IMB(z.id), bar.Time(z.leftBar), z.top, bar.Time(toIndex), z.bottom, txt, col, 0.06, 6, false ); } } //======================== // Liquidity pools //======================== void DetectEqualHighLowPools() { double tol = TolerancePrice(); int end = bar.Size() - 1; if(end < 5) return; for(int i = 0; i <= end; i++) { // Equal highs = BSL if(swing.IsValidValue(swing.HighAt(i))) { for(int j = i + ch3_minPoolSeparationBars; j <= end; j++) { if(!swing.IsValidValue(swing.HighAt(j))) continue; if(NearPrice(bar.High(i), bar.High(j), tol)) { if(CountPoolsOfSide(LQ_BSL) >= ch3_maxPoolsPerType) break; int leftBar = MinInt(i, j); int rightBar = MaxInt(i, j); double px = MathMax(bar.High(i), bar.High(j)); if(!PoolExists(LQ_BSL, LQ_EQUALS, leftBar, rightBar, px)) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_BSL; p.kind = LQ_EQUALS; p.state = LQ_ACTIVE; p.leftBar = leftBar; p.rightBar = rightBar; p.price = px; p.sweepBar = -1; PushPool(p); } } } } // Equal lows = SSL if(swing.IsValidValue(swing.LowAt(i))) { for(int j = i + ch3_minPoolSeparationBars; j <= end; j++) { if(!swing.IsValidValue(swing.LowAt(j))) continue; if(NearPrice(bar.Low(i), bar.Low(j), tol)) { if(CountPoolsOfSide(LQ_SSL) >= ch3_maxPoolsPerType) break; int leftBar = MinInt(i, j); int rightBar = MaxInt(i, j); double px = MathMin(bar.Low(i), bar.Low(j)); if(!PoolExists(LQ_SSL, LQ_EQUALS, leftBar, rightBar, px)) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_SSL; p.kind = LQ_EQUALS; p.state = LQ_ACTIVE; p.leftBar = leftBar; p.rightBar = rightBar; p.price = px; p.sweepBar = -1; PushPool(p); } } } } } } void DetectStructureLiquidity() { // Internal structure highs/lows can also act as liquidity // We take visible swing highs/lows that have not yet been tagged as equals. int end = bar.Size() - 1; if(end < 3) return; for(int i = 1; i < end - 1; i++) { if(swing.IsValidValue(swing.HighAt(i))) { if(CountPoolsOfSide(LQ_BSL) < ch3_maxPoolsPerType) { if(!PoolExists(LQ_BSL, LQ_STRUCTURE, i, i, bar.High(i))) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_BSL; p.kind = LQ_STRUCTURE; p.state = LQ_ACTIVE; p.leftBar = i; p.rightBar = i; p.price = bar.High(i); p.sweepBar = -1; PushPool(p); } } } if(swing.IsValidValue(swing.LowAt(i))) { if(CountPoolsOfSide(LQ_SSL) < ch3_maxPoolsPerType) { if(!PoolExists(LQ_SSL, LQ_STRUCTURE, i, i, bar.Low(i))) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_SSL; p.kind = LQ_STRUCTURE; p.state = LQ_ACTIVE; p.leftBar = i; p.rightBar = i; p.price = bar.Low(i); p.sweepBar = -1; PushPool(p); } } } } } void DetectExternalRangeLiquidity() { if(IsValidBar(protectedHigh)) { if(!PoolExists(LQ_BSL, LQ_EXTERNAL, protectedHigh, protectedHigh, bar.High(protectedHigh))) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_BSL; p.kind = LQ_EXTERNAL; p.state = LQ_ACTIVE; p.leftBar = protectedHigh; p.rightBar = protectedHigh; p.price = bar.High(protectedHigh); p.sweepBar = -1; PushPool(p); } } if(IsValidBar(protectedLow)) { if(!PoolExists(LQ_SSL, LQ_EXTERNAL, protectedLow, protectedLow, bar.Low(protectedLow))) { LiquidityPool p; p.id = nextPoolId++; p.side = LQ_SSL; p.kind = LQ_EXTERNAL; p.state = LQ_ACTIVE; p.leftBar = protectedLow; p.rightBar = protectedLow; p.price = bar.Low(protectedLow); p.sweepBar = -1; PushPool(p); } } } void UpdatePoolSweeps() { int end = bar.Size() - 1; for(int i = 0; i < ArraySize(pools); i++) { if(pools[i].state == LQ_SWEPT) continue; for(int b = pools[i].rightBar; b <= end; b++) { bool swept = false; if(pools[i].side == LQ_BSL) { if(bar.High(b) > pools[i].price) swept = true; } else { if(bar.Low(b) < pools[i].price) swept = true; } if(swept) { pools[i].state = LQ_SWEPT; pools[i].sweepBar = b; break; } } } } void DrawPool(const LiquidityPool &p) { int end = bar.Size() - 1; int toIndex = (p.sweepBar >= 0 ? p.sweepBar : end); string txt; color col; int style = 1; if(p.side == LQ_BSL) { txt = "BSL"; col = clrTomato; } else { txt = "SSL"; col = clrDeepSkyBlue; } if(p.kind == LQ_EQUALS) txt += " EQ"; if(p.kind == LQ_STRUCTURE) txt += " SH/SL"; if(p.kind == LQ_EXTERNAL) txt += " ERL"; if(p.state == LQ_SWEPT) { txt += " SWEPT"; col = clrSilver; } DrawLevel(K_POOL(p.id), p.leftBar, toIndex, p.price, txt, col, style); } void DrawPools() { for(int i = 0; i < ArraySize(pools); i++) DrawPool(pools[i]); } void DrawERLAnchorsOnly() { int end = bar.Size() - 1; if(IsValidBar(protectedHigh)) { DrawLevel(K_ERL_HIGH(), protectedHigh, end, bar.High(protectedHigh), "ERL HIGH", clrTomato, 1); } if(IsValidBar(protectedLow)) { DrawLevel(K_ERL_LOW(), protectedLow, end, bar.Low(protectedLow), "ERL LOW", clrDeepSkyBlue, 1); } } public: C_Chapter3(); ~C_Chapter3() {} void SetExternalRange(int protectedHighIndex, int protectedLowIndex) { protectedHigh = protectedHighIndex; protectedLow = protectedLowIndex; } void OnCalculate(); int OnInit(string s, ENUM_TIMEFRAMES tf_, int lr = 3, int look = 1); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ C_Chapter3::C_Chapter3() { nextImbId = 1; nextPoolId = 1; protectedHigh = -1; protectedLow = -1; ArrayResize(imbalances, 0); ArrayResize(pools, 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int C_Chapter3::OnInit(string s, ENUM_TIMEFRAMES tf_, int lr = 3, int look = 1) { timeframe = tf_; symbol = s; debug.Init(0, "Chapter3", true); if(fairValue.Init("Fair Value Gaps", s, tf_, lr, look) == -1) { Print("Error creating Fair Value indicator: ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void C_Chapter3::OnCalculate() { if(OnNewBar()) return; bar.UpdateFromMonthsBack(symbol, timeframe, 3); ArrayResize(imbalances, 0); ArrayResize(pools, 0); nextImbId = 1; nextPoolId = 1; DetectImbalances(); UpdateImbalanceMitigation(); DetectEqualHighLowPools(); DetectStructureLiquidity(); DetectExternalRangeLiquidity(); UpdatePoolSweeps(); DrawERLAnchorsOnly(); DrawPools(); DrawImbalances(); } //+------------------------------------------------------------------+