1.) Open MetaEditor (MT4 or MT5).
2.) Create a new Custom Indicator and name it InstitutionalLiquidityPro.
Code: Select all
//+------------------------------------------------------------------+
//| InstitutionalLiquidityPro |
//| EQH/EQL Sweeps, Reclaims, & Micro BOS |
//+------------------------------------------------------------------+
#property copyright "Pro Institutional"
#property link ""
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 0
// ==============================================================================
// 1. PARAMETERS
// ==============================================================================
input string grp1 = "--- Liquidity & Structure ---";
input int PLen = 10; // Pivot Lookback Bars
input double PointTol = 15.0; // Equal Tolerance (Points)
input string grp2 = "--- Session Context ---";
input bool UseSession = true; // Highlight Accumulation Session
input int SessionStartHour = 18; // Session Start Hour (Broker Time)
input int SessionEndHour = 2; // Session End Hour (Broker Time)
input color SessionColor = clrMidnightBlue;// Session Background Color
input string grp3 = "--- Risk & UI ---";
input double RrRatio = 2.0; // Risk:Reward Target Multiplier
input bool EnableAlerts = true; // Enable Real-Time Alerts
// ==============================================================================
// 2. STATE MANAGEMENT
// ==============================================================================
struct LiquidityLevel {
double price;
datetime timeStart;
bool isActive;
bool isSwept;
double sweepExtr;
double triggerBOS;
string lineName;
};
LiquidityLevel eqh;
LiquidityLevel eql;
double lastPh = 0, lastPl = 0;
datetime timePh = 0, timePl = 0;
int sessionStartIndex = -1;
bool inSession = false;
// ==============================================================================
// 3. INITIALIZATION & CLEANUP
// ==============================================================================
int OnInit() {
// Build the HUD
CreateHUDLabel("ILP_HUD_TITLE", "INSTITUTIONAL LIQUIDITY [PRO]", clrWhite, 20, 10, true);
CreateHUDLabel("ILP_HUD_EQH", "EQH: Scanning...", clrTomato, 40, 9, false);
CreateHUDLabel("ILP_HUD_EQL", "EQL: Scanning...", clrDodgerBlue, 60, 9, false);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
ObjectsDeleteAll(0, "ILP_");
}
void CreateHUDLabel(string name, string text, color clr, int yDist, int fontSize, bool isBold) {
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDist);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
if(isBold) ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");
else ObjectSetString(0, name, OBJPROP_FONT, "Arial");
}
// ==============================================================================
// 4. CORE CALCULATION LOGIC
// ==============================================================================
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Ensure chronological ordering regardless of MT4 vs MT5 defaults
ArraySetAsSeries(time, false);
ArraySetAsSeries(high, false);
ArraySetAsSeries(low, false);
ArraySetAsSeries(close, false);
if (rates_total < PLen * 2) return 0;
// Wipe state if reloading history
if(prev_calculated == 0) {
ObjectsDeleteAll(0, "ILP_");
OnInit();
lastPh = 0; lastPl = 0; timePh = 0; timePl = 0;
eqh.isActive = false; eqh.isSwept = false;
eql.isActive = false; eql.isSwept = false;
inSession = false;
}
int limit = prev_calculated == 0 ? PLen * 2 : prev_calculated - 1;
for (int i = limit; i < rates_total; i++) {
ProcessSession(i, time, high, low);
DetectPivots(i, time, high, low);
ProcessEQH(i, rates_total, time, high, low, close);
ProcessEQL(i, rates_total, time, high, low, close);
}
UpdateHUD(rates_total);
return(rates_total);
}
// ==============================================================================
// 5. PROCESS METHODS
// ==============================================================================
void DetectPivots(int i, const datetime &time[], const double &high[], const double &low[]) {
int pIdx = i - PLen;
double threshold = PointTol * _Point;
// EQH
bool isPh = true;
for(int k = pIdx - PLen; k <= pIdx + PLen; k++) {
if(k == pIdx) continue;
if(high[k] > high[pIdx]) { isPh = false; break; }
}
if(isPh) {
double ph = high[pIdx];
if(lastPh != 0.0 && MathAbs(ph - lastPh) <= threshold) {
eqh.price = MathMax(ph, lastPh);
eqh.timeStart = timePh;
eqh.isActive = true;
eqh.isSwept = false;
eqh.lineName = "ILP_EQH_" + IntegerToString(time[pIdx]);
ObjectCreate(0, eqh.lineName, OBJ_TREND, 0, eqh.timeStart, eqh.price, time[pIdx], eqh.price);
ObjectSetInteger(0, eqh.lineName, OBJPROP_COLOR, clrTomato);
ObjectSetInteger(0, eqh.lineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, eqh.lineName, OBJPROP_RAY_RIGHT, true);
}
lastPh = ph; timePh = time[pIdx];
}
// EQL
bool isPl = true;
for(int k = pIdx - PLen; k <= pIdx + PLen; k++) {
if(k == pIdx) continue;
if(low[k] < low[pIdx]) { isPl = false; break; }
}
if(isPl) {
double pl = low[pIdx];
if(lastPl != 0.0 && MathAbs(pl - lastPl) <= threshold) {
eql.price = MathMin(pl, lastPl);
eql.timeStart = timePl;
eql.isActive = true;
eql.isSwept = false;
eql.lineName = "ILP_EQL_" + IntegerToString(time[pIdx]);
ObjectCreate(0, eql.lineName, OBJ_TREND, 0, eql.timeStart, eql.price, time[pIdx], eql.price);
ObjectSetInteger(0, eql.lineName, OBJPROP_COLOR, clrDodgerBlue);
ObjectSetInteger(0, eql.lineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, eql.lineName, OBJPROP_RAY_RIGHT, true);
}
lastPl = pl; timePl = time[pIdx];
}
}
void ProcessEQH(int i, int rates_total, const datetime &time[], const double &high[], const double &low[], const double &close[]) {
if(!eqh.isActive) return;
double h = high[i], l = low[i], c = close[i];
double pc = i > 0 ? close[i-1] : close[i];
if(h > eqh.price && !eqh.isSwept) {
eqh.isSwept = true;
eqh.sweepExtr = h;
eqh.triggerBOS = l;
ObjectSetInteger(0, eqh.lineName, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, eqh.lineName, OBJPROP_TIME2, time[i]);
ObjectSetInteger(0, eqh.lineName, OBJPROP_COLOR, clrGray);
ObjectSetInteger(0, eqh.lineName, OBJPROP_STYLE, STYLE_DOT);
if(c < eqh.price) TriggerExecution("EQH Reclaim", i, rates_total, eqh.sweepExtr, true, time, high, low, close);
}
else if(eqh.isSwept) {
if(h > eqh.sweepExtr) eqh.sweepExtr = h;
if(h > high[i-1] && l > eqh.triggerBOS) eqh.triggerBOS = l;
if(c < eqh.price && pc >= eqh.price)
TriggerExecution("EQH Delayed Reclaim", i, rates_total, eqh.sweepExtr, true, time, high, low, close);
else if(c < eqh.triggerBOS && pc >= eqh.triggerBOS)
TriggerExecution("EQH Micro BOS", i, rates_total, eqh.sweepExtr, true, time, high, low, close);
}
}
void ProcessEQL(int i, int rates_total, const datetime &time[], const double &high[], const double &low[], const double &close[]) {
if(!eql.isActive) return;
double h = high[i], l = low[i], c = close[i];
double pc = i > 0 ? close[i-1] : close[i];
if(l < eql.price && !eql.isSwept) {
eql.isSwept = true;
eql.sweepExtr = l;
eql.triggerBOS = h;
ObjectSetInteger(0, eql.lineName, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, eql.lineName, OBJPROP_TIME2, time[i]);
ObjectSetInteger(0, eql.lineName, OBJPROP_COLOR, clrGray);
ObjectSetInteger(0, eql.lineName, OBJPROP_STYLE, STYLE_DOT);
if(c > eql.price) TriggerExecution("EQL Reclaim", i, rates_total, eql.sweepExtr, false, time, high, low, close);
}
else if(eql.isSwept) {
if(l < eql.sweepExtr) eql.sweepExtr = l;
if(l < low[i-1] && h < eql.triggerBOS) eql.triggerBOS = h;
if(c > eql.price && pc <= eql.price)
TriggerExecution("EQL Delayed Reclaim", i, rates_total, eql.sweepExtr, false, time, high, low, close);
else if(c > eql.triggerBOS && pc <= eql.triggerBOS)
TriggerExecution("EQL Micro BOS", i, rates_total, eql.sweepExtr, false, time, high, low, close);
}
}
void TriggerExecution(string signal, int i, int rates_total, double stopLoss, bool isShort, const datetime &time[], const double &high[], const double &low[], const double &close[]) {
double entry = close[i];
double risk = MathAbs(entry - stopLoss);
double target = isShort ? entry - (risk * RrRatio) : entry + (risk * RrRatio);
string uid = IntegerToString(time[i]);
// 1. Entry Text Label
string lblName = "ILP_LBL_" + uid;
double yPos = isShort ? high[i] + (10 * _Point) : low[i] - (10 * _Point);
ObjectCreate(0, lblName, OBJ_TEXT, 0, time[i], yPos);
ObjectSetString(0, lblName, OBJPROP_TEXT, signal + " SL: " + DoubleToString(stopLoss, _Digits));
ObjectSetInteger(0, lblName, OBJPROP_COLOR, isShort ? clrTomato : clrDodgerBlue);
// 2. TP Risk/Reward Box
string boxName = "ILP_TP_" + uid;
datetime tpTime = time[i] + (PeriodSeconds() * 10);
ObjectCreate(0, boxName, OBJ_RECTANGLE, 0, time[i], entry, tpTime, target);
ObjectSetInteger(0, boxName, OBJPROP_COLOR, clrMediumSeaGreen);
ObjectSetInteger(0, boxName, OBJPROP_BACK, true);
// 3. Real-Time Alert
if(EnableAlerts && i == rates_total - 1) {
Alert("[LIQUIDITY SWEEP] ", signal, " at ", DoubleToString(entry, _Digits));
}
if(isShort) eqh.isActive = false; else eql.isActive = false;
}
void ProcessSession(int i, const datetime &time[], const double &high[], const double &low[]) {
if(!UseSession) return;
int h;
#ifdef __MQL5__
MqlDateTime dt; TimeToStruct(time[i], dt); h = dt.hour;
#else
h = TimeHour(time[i]);
#endif
bool currentInSession = (SessionStartHour > SessionEndHour) ?
(h >= SessionStartHour || h < SessionEndHour) :
(h >= SessionStartHour && h < SessionEndHour);
if(currentInSession && !inSession) {
sessionStartIndex = i;
inSession = true;
}
else if(!currentInSession && inSession) {
double sessHigh = -999999, sessLow = 999999;
for(int k = sessionStartIndex; k < i; k++) {
if(high[k] > sessHigh) sessHigh = high[k];
if(low[k] < sessLow) sessLow = low[k];
}
string boxName = "ILP_Sess_" + IntegerToString(time[sessionStartIndex]);
ObjectCreate(0, boxName, OBJ_RECTANGLE, 0, time[sessionStartIndex], sessHigh, time[i], sessLow);
ObjectSetInteger(0, boxName, OBJPROP_COLOR, SessionColor);
ObjectSetInteger(0, boxName, OBJPROP_BACK, true);
inSession = false;
}
}
void UpdateHUD(int rates_total) {
if(rates_total == 0) return;
string eqhTxt = "EQH: ";
if(!eqh.isActive) eqhTxt += "None";
else if(eqh.isSwept) eqhTxt += "Swept! (BOS Trigger: " + DoubleToString(eqh.triggerBOS, _Digits) + ")";
else eqhTxt += "Magnet @ " + DoubleToString(eqh.price, _Digits);
ObjectSetString(0, "ILP_HUD_EQH", OBJPROP_TEXT, eqhTxt);
string eqlTxt = "EQL: ";
if(!eql.isActive) eqlTxt += "None";
else if(eql.isSwept) eqlTxt += "Swept! (BOS Trigger: " + DoubleToString(eql.triggerBOS, _Digits) + ")";
else eqlTxt += "Magnet @ " + DoubleToString(eql.price, _Digits);
ObjectSetString(0, "ILP_HUD_EQL", OBJPROP_TEXT, eqlTxt);
ChartRedraw(0);
}