mql5/Experts/AnotherTriBible/Chapter3.mqh
younglubah-gmail 540d58914e
2026-03-15 02:46:38 +02:00

558 lines
No EOL
16 KiB
MQL5

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