1228 lines
38 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| XAUUSD_MTF_LIQUIDITY_OB_REVERSAL.mq5 |
| |||
//| Multi-TF Liquidity + Order Block + M5 Reversal/Breakout |
| |||
//| Previous Day / H4 / M30 / M15 / M5 + M1 Swing Trade Levels |
| |||
//+------------------------------------------------------------------+
| |||
#property strict
| |||
#property indicator_chart_window
| |||
#property indicator_plots 0
| |||
#property version "1.00"
| |||
| |||
input ENUM_TIMEFRAMES InpExecutionTF = PERIOD_M5;
| |||
input ENUM_TIMEFRAMES InpSwingTF = PERIOD_M1;
| |||
| |||
input int InpLookbackPD = 20;
| |||
input int InpLookbackH4 = 80;
| |||
input int InpLookbackM30 = 100;
| |||
input int InpLookbackM15 = 120;
| |||
input int InpLookbackM5 = 150;
| |||
| |||
input int InpPivotStrength = 2;
| |||
input int InpM1PivotStrength = 2;
| |||
input int InpOBLookback = 30;
| |||
input int InpDisplacementBars = 2;
| |||
| |||
input double InpMatchDistance = 1.50; // price distance for XAUUSD
| |||
input bool InpUseATRMatch = true;
| |||
input double InpATRMatchMultiplier = 0.20;
| |||
input int InpATRPeriod = 14;
| |||
| |||
input bool InpRequireSweep = true;
| |||
input bool InpRequireClose = true;
| |||
input bool InpRequireDisplace = true;
| |||
input bool InpAllowBreakout = true;
| |||
| |||
input int InpMaxZonesPerSide = 2;
| |||
input int InpZoneForwardBars = 80;
| |||
| |||
input bool InpShowPDLevels = true;
| |||
input bool InpShowHTFLevels = false;
| |||
input bool InpShowOBZones = true;
| |||
input bool InpShowSweepLines = true;
| |||
input bool InpShowPositionTools = true;
| |||
input bool InpShowPriceText = true;
| |||
| |||
input bool InpAlerts = true;
| |||
input bool InpPushNotification = false;
| |||
input bool InpSound = true;
| |||
input string InpSoundFile = "alert.wav";
| |||
| |||
input color InpZoneColor = clrLightGray;
| |||
input color InpBuyColor = clrLimeGreen;
| |||
input color InpSellColor = clrTomato;
| |||
input color InpEntryColor = clrDodgerBlue;
| |||
input color InpSLColor = clrRed;
| |||
input color InpTPColor = clrLimeGreen;
| |||
input color InpSweepColor = clrGold;
| |||
| |||
input int InpLineWidth = 1;
| |||
input ENUM_LINE_STYLE InpLineStyle = STYLE_DOT;
| |||
| |||
input string InpObjectPrefix = "MTF_LQ_OB_";
| |||
| |||
//---------------------------- structures ----------------------------//
| |||
struct LevelInfo
| |||
{
| |||
double price;
| |||
ENUM_TIMEFRAMES tf;
| |||
bool buySide; // true = buy-side liquidity / highs
| |||
int strength;
| |||
string source;
| |||
};
| |||
| |||
struct ZoneInfo
| |||
{
| |||
double high;
| |||
double low;
| |||
double mid;
| |||
bool buySide; // true = buy-side zone, false = sell-side
| |||
int strength;
| |||
string text;
| |||
};
| |||
| |||
struct TradeSetup
| |||
{
| |||
bool valid;
| |||
bool isBuy;
| |||
datetime signalTime;
| |||
double entry;
| |||
double sl;
| |||
double tp1;
| |||
double tp2;
| |||
double tp3;
| |||
string reason;
| |||
};
| |||
| |||
//---------------------------- globals --------------------------------//
| |||
datetime g_lastM5Bar = 0;
| |||
datetime g_lastSignalBar = 0;
| |||
int g_signalCounter = 0;
| |||
| |||
ZoneInfo g_buyZones[2];
| |||
ZoneInfo g_sellZones[2];
| |||
int g_buyZoneCount = 0;
| |||
int g_sellZoneCount = 0;
| |||
| |||
double g_pdh=0, g_pdl=0;
| |||
double g_h4High=0, g_h4Low=0;
| |||
double g_m30High=0, g_m30Low=0;
| |||
double g_m15High=0, g_m15Low=0;
| |||
double g_m5High=0, g_m5Low=0;
| |||
| |||
bool g_buyRunning=false;
| |||
bool g_sellRunning=false;
| |||
bool g_buyTP1=false;
| |||
bool g_sellTP1=false;
| |||
double g_activeBuyEntry=0, g_activeBuySL=0, g_activeBuyTP1=0;
| |||
double g_activeSellEntry=0, g_activeSellSL=0, g_activeSellTP1=0;
| |||
| |||
string g_status="WAITING";
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Utility |
| |||
//+------------------------------------------------------------------+
| |||
string TFName(ENUM_TIMEFRAMES tf)
| |||
{
| |||
switch(tf)
| |||
{
| |||
case PERIOD_M1: return "M1";
| |||
case PERIOD_M5: return "M5";
| |||
case PERIOD_M15: return "M15";
| |||
case PERIOD_M30: return "M30";
| |||
case PERIOD_H1: return "H1";
| |||
case PERIOD_H4: return "H4";
| |||
case PERIOD_D1: return "D1";
| |||
}
| |||
return EnumToString(tf);
| |||
}
| |||
| |||
string PriceStr(double p)
| |||
{
| |||
return DoubleToString(p,_Digits);
| |||
}
| |||
| |||
double NormalizePrice(double p)
| |||
{
| |||
return NormalizeDouble(p,_Digits);
| |||
}
| |||
| |||
double MatchDistance()
| |||
{
| |||
if(!InpUseATRMatch)
| |||
return InpMatchDistance;
| |||
| |||
double atr=GetATR(_Symbol,InpExecutionTF,InpATRPeriod,1);
| |||
if(atr<=0)
| |||
return InpMatchDistance;
| |||
| |||
double d=atr*InpATRMatchMultiplier;
| |||
return MathMax(d,SymbolInfoDouble(_Symbol,SYMBOL_POINT)*20.0);
| |||
}
| |||
| |||
bool IsNewExecutionBar()
| |||
{
| |||
datetime t=iTime(_Symbol,InpExecutionTF,0);
| |||
if(t==0) return false;
| |||
if(t!=g_lastM5Bar)
| |||
{
| |||
g_lastM5Bar=t;
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
| |||
void DeleteObjectsByPrefix(string prefix)
| |||
{
| |||
int total=ObjectsTotal(0,-1,-1);
| |||
for(int i=total-1;i>=0;i--)
| |||
{
| |||
string n=ObjectName(0,i,-1,-1);
| |||
if(StringFind(n,prefix)==0)
| |||
ObjectDelete(0,n);
| |||
}
| |||
}
| |||
| |||
void DeleteCurrentZoneObjects()
| |||
{
| |||
DeleteObjectsByPrefix(InpObjectPrefix+"ZONE_");
| |||
DeleteObjectsByPrefix(InpObjectPrefix+"HTF_");
| |||
}
| |||
| |||
bool CreateHLine(string name,double price,color clr,string text="",ENUM_LINE_STYLE style=STYLE_DOT,int width=1)
| |||
{
| |||
if(ObjectFind(0,name)>=0) ObjectDelete(0,name);
| |||
if(!ObjectCreate(0,name,OBJ_HLINE,0,0,price))
| |||
return false;
| |||
| |||
ObjectSetDouble(0,name,OBJPROP_PRICE,price);
| |||
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |||
ObjectSetInteger(0,name,OBJPROP_STYLE,style);
| |||
ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
| |||
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,name,OBJPROP_BACK,false);
| |||
ObjectSetString(0,name,OBJPROP_TOOLTIP,text);
| |||
return true;
| |||
}
| |||
| |||
bool CreateText(string name,datetime t,double price,string text,color clr,int size=8)
| |||
{
| |||
if(ObjectFind(0,name)>=0) ObjectDelete(0,name);
| |||
if(!ObjectCreate(0,name,OBJ_TEXT,0,t,price))
| |||
return false;
| |||
| |||
ObjectSetString(0,name,OBJPROP_TEXT,text);
| |||
ObjectSetString(0,name,OBJPROP_FONT,"Consolas");
| |||
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,size);
| |||
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |||
ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT);
| |||
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,name,OBJPROP_BACK,false);
| |||
return true;
| |||
}
| |||
| |||
bool CreateRectangle(string name,datetime t1,double p1,datetime t2,double p2,color clr)
| |||
{
| |||
if(ObjectFind(0,name)>=0) ObjectDelete(0,name);
| |||
if(!ObjectCreate(0,name,OBJ_RECTANGLE,0,t1,p1,t2,p2))
| |||
return false;
| |||
| |||
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |||
ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
| |||
ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
| |||
ObjectSetInteger(0,name,OBJPROP_FILL,true);
| |||
ObjectSetInteger(0,name,OBJPROP_BACK,true);
| |||
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |||
return true;
| |||
}
| |||
| |||
bool CreateTrend(string name,datetime t1,double p1,datetime t2,double p2,color clr,
| |||
ENUM_LINE_STYLE style=STYLE_SOLID,int width=1,bool ray=false)
| |||
{
| |||
if(ObjectFind(0,name)>=0) ObjectDelete(0,name);
| |||
if(!ObjectCreate(0,name,OBJ_TREND,0,t1,p1,t2,p2))
| |||
return false;
| |||
| |||
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
| |||
ObjectSetInteger(0,name,OBJPROP_STYLE,style);
| |||
ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
| |||
ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,ray);
| |||
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ATR |
| |||
//+------------------------------------------------------------------+
| |||
double GetATR(string symbol,ENUM_TIMEFRAMES tf,int period,int shift)
| |||
{
| |||
int h=iATR(symbol,tf,period);
| |||
if(h==INVALID_HANDLE) return 0;
| |||
| |||
double b[];
| |||
ArraySetAsSeries(b,true);
| |||
double v=0;
| |||
if(CopyBuffer(h,0,shift,1,b)==1)
| |||
v=b[0];
| |||
IndicatorRelease(h);
| |||
return v;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Rates helpers |
| |||
//+------------------------------------------------------------------+
| |||
bool GetRates(ENUM_TIMEFRAMES tf,int count,MqlRates &r[])
| |||
{
| |||
ArraySetAsSeries(r,true);
| |||
int copied=CopyRates(_Symbol,tf,0,count,r);
| |||
return copied>=MathMin(count,10);
| |||
}
| |||
| |||
double HighestHigh(ENUM_TIMEFRAMES tf,int lookback,int startShift=1)
| |||
{
| |||
MqlRates r[];
| |||
int n=lookback+startShift+5;
| |||
if(!GetRates(tf,n,r)) return 0;
| |||
double v=-DBL_MAX;
| |||
int maxIndex=MathMin(lookback+startShift,ArraySize(r)-1);
| |||
for(int i=startShift;i<=maxIndex;i++)
| |||
if(r[i].high>v) v=r[i].high;
| |||
return (v==-DBL_MAX?0:v);
| |||
}
| |||
| |||
double LowestLow(ENUM_TIMEFRAMES tf,int lookback,int startShift=1)
| |||
{
| |||
MqlRates r[];
| |||
int n=lookback+startShift+5;
| |||
if(!GetRates(tf,n,r)) return 0;
| |||
double v=DBL_MAX;
| |||
int maxIndex=MathMin(lookback+startShift,ArraySize(r)-1);
| |||
for(int i=startShift;i<=maxIndex;i++)
| |||
if(r[i].low<v) v=r[i].low;
| |||
return (v==DBL_MAX?0:v);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Previous day |
| |||
//+------------------------------------------------------------------+
| |||
void CalculatePreviousDay()
| |||
{
| |||
MqlRates d[];
| |||
ArraySetAsSeries(d,true);
| |||
if(CopyRates(_Symbol,PERIOD_D1,0,3,d)<3) return;
| |||
g_pdh=d[1].high;
| |||
g_pdl=d[1].low;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Pivot tests |
| |||
//+------------------------------------------------------------------+
| |||
bool IsSwingHigh(MqlRates &r[],int i,int strength)
| |||
{
| |||
int n=ArraySize(r);
| |||
if(i-strength<0 || i+strength>=n) return false;
| |||
for(int k=1;k<=strength;k++)
| |||
{
| |||
if(r[i].high<=r[i-k].high) return false;
| |||
if(r[i].high<=r[i+k].high) return false;
| |||
}
| |||
return true;
| |||
}
| |||
| |||
bool IsSwingLow(MqlRates &r[],int i,int strength)
| |||
{
| |||
int n=ArraySize(r);
| |||
if(i-strength<0 || i+strength>=n) return false;
| |||
for(int k=1;k<=strength;k++)
| |||
{
| |||
if(r[i].low>=r[i-k].low) return false;
| |||
if(r[i].low>=r[i+k].low) return false;
| |||
}
| |||
return true;
| |||
}
| |||
| |||
double LastSwingHigh(ENUM_TIMEFRAMES tf,int strength,int startShift=2,int maxBars=300)
| |||
{
| |||
MqlRates r[];
| |||
if(!GetRates(tf,maxBars,r)) return 0;
| |||
int last=MathMin(maxBars-strength-1,ArraySize(r)-strength-1);
| |||
for(int i=startShift;i<=last;i++)
| |||
if(IsSwingHigh(r,i,strength))
| |||
return r[i].high;
| |||
return 0;
| |||
}
| |||
| |||
double LastSwingLow(ENUM_TIMEFRAMES tf,int strength,int startShift=2,int maxBars=300)
| |||
{
| |||
MqlRates r[];
| |||
if(!GetRates(tf,maxBars,r)) return 0;
| |||
int last=MathMin(maxBars-strength-1,ArraySize(r)-strength-1);
| |||
for(int i=startShift;i<=last;i++)
| |||
if(IsSwingLow(r,i,strength))
| |||
return r[i].low;
| |||
return 0;
| |||
}
| |||
| |||
// Find next swing highs/lows after entry in chronological/price search
| |||
int FindSwingTargets(bool isBuy,double entry,double &targets[],int maxTargets=3)
| |||
{
| |||
ArrayResize(targets,maxTargets);
| |||
for(int j=0;j<maxTargets;j++) targets[j]=0;
| |||
| |||
MqlRates r[];
| |||
if(!GetRates(InpSwingTF,500,r)) return 0;
| |||
| |||
double vals[];
| |||
ArrayResize(vals,0);
| |||
| |||
// Start from most recent closed candle and search confirmed pivots.
| |||
for(int i=InpM1PivotStrength+1;i<ArraySize(r)-InpM1PivotStrength;i++)
| |||
{
| |||
if(isBuy)
| |||
{
| |||
if(IsSwingHigh(r,i,InpM1PivotStrength) && r[i].high>entry)
| |||
{
| |||
int n=ArraySize(vals);
| |||
ArrayResize(vals,n+1);
| |||
vals[n]=r[i].high;
| |||
}
| |||
}
| |||
else
| |||
{
| |||
if(IsSwingLow(r,i,InpM1PivotStrength) && r[i].low<entry)
| |||
{
| |||
int n=ArraySize(vals);
| |||
ArrayResize(vals,n+1);
| |||
vals[n]=r[i].low;
| |||
}
| |||
}
| |||
if(ArraySize(vals)>=maxTargets+8) break;
| |||
}
| |||
| |||
// Sort nearest-to-entry first.
| |||
int n=ArraySize(vals);
| |||
for(int a=0;a<n-1;a++)
| |||
for(int b=a+1;b<n;b++)
| |||
{
| |||
bool swap=false;
| |||
if(isBuy && vals[b]<vals[a]) swap=true;
| |||
if(!isBuy && vals[b]>vals[a]) swap=true;
| |||
if(swap)
| |||
{
| |||
double tmp=vals[a]; vals[a]=vals[b]; vals[b]=tmp;
| |||
}
| |||
}
| |||
| |||
int out=0;
| |||
double last=0;
| |||
for(int i=0;i<n && out<maxTargets;i++)
| |||
{
| |||
if(last==0 || MathAbs(vals[i]-last)>SymbolInfoDouble(_Symbol,SYMBOL_POINT)*5)
| |||
{
| |||
targets[out]=NormalizePrice(vals[i]);
| |||
last=vals[i];
| |||
out++;
| |||
}
| |||
}
| |||
return out;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Build level lists |
| |||
//+------------------------------------------------------------------+
| |||
void AddLevel(LevelInfo &arr[],double price,ENUM_TIMEFRAMES tf,bool buySide,string source)
| |||
{
| |||
if(price<=0) return;
| |||
int n=ArraySize(arr);
| |||
ArrayResize(arr,n+1);
| |||
arr[n].price=price;
| |||
arr[n].tf=tf;
| |||
arr[n].buySide=buySide;
| |||
arr[n].strength=1;
| |||
arr[n].source=source;
| |||
}
| |||
| |||
void AddExtremeLevels(ENUM_TIMEFRAMES tf,int lookback,LevelInfo &buy[],LevelInfo &sell[])
| |||
{
| |||
MqlRates r[];
| |||
if(!GetRates(tf,lookback+20,r)) return;
| |||
| |||
int maxI=MathMin(lookback,ArraySize(r)-InpPivotStrength-2);
| |||
int foundH=0,foundL=0;
| |||
| |||
for(int i=InpPivotStrength+1;i<=maxI;i++)
| |||
{
| |||
if(IsSwingHigh(r,i,InpPivotStrength))
| |||
{
| |||
AddLevel(buy,r[i].high,tf,true,TFName(tf)+" Swing High");
| |||
foundH++;
| |||
if(foundH>=6) break;
| |||
}
| |||
}
| |||
| |||
for(int i=InpPivotStrength+1;i<=maxI;i++)
| |||
{
| |||
if(IsSwingLow(r,i,InpPivotStrength))
| |||
{
| |||
AddLevel(sell,r[i].low,tf,false,TFName(tf)+" Swing Low");
| |||
foundL++;
| |||
if(foundL>=6) break;
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Cluster matching |
| |||
//+------------------------------------------------------------------+
| |||
void BuildZones()
| |||
{
| |||
g_buyZoneCount=0;
| |||
g_sellZoneCount=0;
| |||
| |||
LevelInfo buy[],sell[];
| |||
ArrayResize(buy,0);
| |||
ArrayResize(sell,0);
| |||
| |||
// Previous day exact levels
| |||
AddLevel(buy,g_pdh,PERIOD_D1,true,"PDH");
| |||
AddLevel(sell,g_pdl,PERIOD_D1,false,"PDL");
| |||
| |||
AddExtremeLevels(PERIOD_H4,InpLookbackH4,buy,sell);
| |||
AddExtremeLevels(PERIOD_M30,InpLookbackM30,buy,sell);
| |||
AddExtremeLevels(PERIOD_M15,InpLookbackM15,buy,sell);
| |||
AddExtremeLevels(PERIOD_M5,InpLookbackM5,buy,sell);
| |||
| |||
double dist=MatchDistance();
| |||
| |||
// Cluster each level with other TF levels.
| |||
for(int i=0;i<ArraySize(buy);i++)
| |||
{
| |||
int score=0;
| |||
string src=buy[i].source;
| |||
double sum=buy[i].price;
| |||
| |||
for(int j=0;j<ArraySize(buy);j++)
| |||
{
| |||
if(i==j) continue;
| |||
if(MathAbs(buy[i].price-buy[j].price)<=dist)
| |||
{
| |||
score++;
| |||
sum+=buy[j].price;
| |||
src+="+"+buy[j].source;
| |||
}
| |||
}
| |||
| |||
buy[i].strength=score+1;
| |||
buy[i].price=NormalizePrice(sum/(score+1));
| |||
}
| |||
| |||
for(int i=0;i<ArraySize(sell);i++)
| |||
{
| |||
int score=0;
| |||
string src=sell[i].source;
| |||
double sum=sell[i].price;
| |||
| |||
for(int j=0;j<ArraySize(sell);j++)
| |||
{
| |||
if(i==j) continue;
| |||
if(MathAbs(sell[i].price-sell[j].price)<=dist)
| |||
{
| |||
score++;
| |||
sum+=sell[j].price;
| |||
src+="+"+sell[j].source;
| |||
}
| |||
}
| |||
| |||
sell[i].strength=score+1;
| |||
sell[i].price=NormalizePrice(sum/(score+1));
| |||
}
| |||
| |||
// Sort descending strength
| |||
for(int i=0;i<ArraySize(buy)-1;i++)
| |||
for(int j=i+1;j<ArraySize(buy);j++)
| |||
if(buy[j].strength>buy[i].strength)
| |||
{
| |||
LevelInfo t=buy[i]; buy[i]=buy[j]; buy[j]=t;
| |||
}
| |||
| |||
for(int i=0;i<ArraySize(sell)-1;i++)
| |||
for(int j=i+1;j<ArraySize(sell);j++)
| |||
if(sell[j].strength>sell[i].strength)
| |||
{
| |||
LevelInfo t=sell[i]; sell[i]=sell[j]; sell[j]=t;
| |||
}
| |||
| |||
// Keep unique strongest clusters
| |||
for(int i=0;i<ArraySize(buy) && g_buyZoneCount<InpMaxZonesPerSide;i++)
| |||
{
| |||
bool unique=true;
| |||
for(int j=0;j<g_buyZoneCount;j++)
| |||
if(MathAbs(buy[i].price-g_buyZones[j].mid)<=dist*0.75)
| |||
unique=false;
| |||
| |||
if(!unique) continue;
| |||
| |||
g_buyZones[g_buyZoneCount].mid=buy[i].price;
| |||
g_buyZones[g_buyZoneCount].high=buy[i].price+dist*0.45;
| |||
g_buyZones[g_buyZoneCount].low =buy[i].price-dist*0.45;
| |||
g_buyZones[g_buyZoneCount].buySide=true;
| |||
g_buyZones[g_buyZoneCount].strength=buy[i].strength;
| |||
g_buyZones[g_buyZoneCount].text=buy[i].source;
| |||
g_buyZoneCount++;
| |||
}
| |||
| |||
for(int i=0;i<ArraySize(sell) && g_sellZoneCount<InpMaxZonesPerSide;i++)
| |||
{
| |||
bool unique=true;
| |||
for(int j=0;j<g_sellZoneCount;j++)
| |||
if(MathAbs(sell[i].price-g_sellZones[j].mid)<=dist*0.75)
| |||
unique=false;
| |||
| |||
if(!unique) continue;
| |||
| |||
g_sellZones[g_sellZoneCount].mid=sell[i].price;
| |||
g_sellZones[g_sellZoneCount].high=sell[i].price+dist*0.45;
| |||
g_sellZones[g_sellZoneCount].low =sell[i].price-dist*0.45;
| |||
g_sellZones[g_sellZoneCount].buySide=false;
| |||
g_sellZones[g_sellZoneCount].strength=sell[i].strength;
| |||
g_sellZones[g_sellZoneCount].text=sell[i].source;
| |||
g_sellZoneCount++;
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Order block finder |
| |||
//+------------------------------------------------------------------+
| |||
bool FindOrderBlock(bool bullish,double reference,double &obHigh,double &obLow)
| |||
{
| |||
MqlRates r[];
| |||
if(!GetRates(InpExecutionTF,InpOBLookback+10,r)) return false;
| |||
| |||
double atr=GetATR(_Symbol,InpExecutionTF,InpATRPeriod,1);
| |||
if(atr<=0) atr=MatchDistance();
| |||
| |||
// Search recent opposite candle near reference.
| |||
for(int i=2;i<InpOBLookback && i<ArraySize(r)-2;i++)
| |||
{
| |||
double body=MathAbs(r[i].close-r[i].open);
| |||
bool opposite = bullish ? (r[i].close<r[i].open) : (r[i].close>r[i].open);
| |||
| |||
if(!opposite) continue;
| |||
| |||
if(MathAbs(r[i].high-reference)<=atr*3.0 ||
| |||
MathAbs(r[i].low-reference)<=atr*3.0)
| |||
{
| |||
// Require following candle displacement in intended direction.
| |||
bool displacement=false;
| |||
if(bullish)
| |||
displacement=(r[i-1].close>r[i].high && MathAbs(r[i-1].close-r[i-1].open)>=body*0.8);
| |||
else
| |||
displacement=(r[i-1].close<r[i].low && MathAbs(r[i-1].close-r[i-1].open)>=body*0.8);
| |||
| |||
if(displacement || !InpRequireDisplace)
| |||
{
| |||
obHigh=r[i].high;
| |||
obLow=r[i].low;
| |||
return true;
| |||
}
| |||
}
| |||
}
| |||
return false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Draw current zones |
| |||
//+------------------------------------------------------------------+
| |||
void DrawZones()
| |||
{
| |||
DeleteCurrentZoneObjects();
| |||
| |||
datetime t1=iTime(_Symbol,InpExecutionTF,InpLookbackM5);
| |||
if(t1==0) t1=iTime(_Symbol,InpExecutionTF,InpMaxZonesPerSide);
| |||
datetime t2=iTime(_Symbol,InpExecutionTF,0)+PeriodSeconds(InpExecutionTF)*InpZoneForwardBars;
| |||
| |||
double dist=MatchDistance();
| |||
| |||
for(int i=0;i<g_buyZoneCount;i++)
| |||
{
| |||
string base=InpObjectPrefix+"ZONE_BUY_"+IntegerToString(i);
| |||
double hi=g_buyZones[i].high;
| |||
double lo=g_buyZones[i].low;
| |||
| |||
CreateRectangle(base,t1,hi,t2,lo,InpZoneColor);
| |||
| |||
if(InpShowPriceText)
| |||
{
| |||
string txt="BUY-SIDE STRONG "+IntegerToString(g_buyZones[i].strength)+
| |||
" | "+PriceStr(g_buyZones[i].mid);
| |||
CreateText(base+"_TXT",TimeCurrent(),g_buyZones[i].mid,txt,InpBuyColor,8);
| |||
}
| |||
| |||
double oh,ol;
| |||
if(InpShowOBZones && FindOrderBlock(true,g_buyZones[i].mid,oh,ol))
| |||
{
| |||
CreateRectangle(base+"_OB",t1,oh,t2,ol,InpZoneColor);
| |||
if(InpShowPriceText)
| |||
CreateText(base+"_OBTXT",TimeCurrent(),(oh+ol)/2,
| |||
"BULLISH OB "+PriceStr((oh+ol)/2),InpBuyColor,8);
| |||
}
| |||
}
| |||
| |||
for(int i=0;i<g_sellZoneCount;i++)
| |||
{
| |||
string base=InpObjectPrefix+"ZONE_SELL_"+IntegerToString(i);
| |||
double hi=g_sellZones[i].high;
| |||
double lo=g_sellZones[i].low;
| |||
| |||
CreateRectangle(base,t1,hi,t2,lo,InpZoneColor);
| |||
| |||
if(InpShowPriceText)
| |||
{
| |||
string txt="SELL-SIDE STRONG "+IntegerToString(g_sellZones[i].strength)+
| |||
" | "+PriceStr(g_sellZones[i].mid);
| |||
CreateText(base+"_TXT",TimeCurrent(),g_sellZones[i].mid,txt,InpSellColor,8);
| |||
}
| |||
| |||
double oh,ol;
| |||
if(InpShowOBZones && FindOrderBlock(false,g_sellZones[i].mid,oh,ol))
| |||
{
| |||
CreateRectangle(base+"_OB",t1,oh,t2,ol,InpZoneColor);
| |||
if(InpShowPriceText)
| |||
CreateText(base+"_OBTXT",TimeCurrent(),(oh+ol)/2,
| |||
"BEARISH OB "+PriceStr((oh+ol)/2),InpSellColor,8);
| |||
}
| |||
}
| |||
| |||
if(InpShowPDLevels)
| |||
{
| |||
CreateHLine(InpObjectPrefix+"ZONE_PDH",g_pdh,InpSellColor,"PDH "+PriceStr(g_pdh),InpLineStyle,1);
| |||
CreateHLine(InpObjectPrefix+"ZONE_PDL",g_pdl,InpBuyColor,"PDL "+PriceStr(g_pdl),InpLineStyle,1);
| |||
| |||
if(InpShowPriceText)
| |||
{
| |||
CreateText(InpObjectPrefix+"ZONE_PDH_TXT",TimeCurrent(),g_pdh,
| |||
"PDH BSL "+PriceStr(g_pdh),InpSellColor,8);
| |||
CreateText(InpObjectPrefix+"ZONE_PDL_TXT",TimeCurrent(),g_pdl,
| |||
"PDL SSL "+PriceStr(g_pdl),InpBuyColor,8);
| |||
}
| |||
}
| |||
| |||
ChartRedraw();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| M5 signal candle logic |
| |||
//+------------------------------------------------------------------+
| |||
bool BullishDisplacement(MqlRates &r[])
| |||
{
| |||
if(ArraySize(r)<5) return false;
| |||
double atr=GetATR(_Symbol,InpExecutionTF,InpATRPeriod,1);
| |||
double body=MathAbs(r[1].close-r[1].open);
| |||
if(r[1].close<=r[1].open) return false;
| |||
if(!InpRequireDisplace) return true;
| |||
return (body>=atr*0.45 && r[1].close>r[2].high);
| |||
}
| |||
| |||
bool BearishDisplacement(MqlRates &r[])
| |||
{
| |||
if(ArraySize(r)<5) return false;
| |||
double atr=GetATR(_Symbol,InpExecutionTF,InpATRPeriod,1);
| |||
double body=MathAbs(r[1].close-r[1].open);
| |||
if(r[1].close>=r[1].open) return false;
| |||
if(!InpRequireDisplace) return true;
| |||
return (body>=atr*0.45 && r[1].close<r[2].low);
| |||
}
| |||
| |||
bool GetSignal(TradeSetup &sig)
| |||
{
| |||
sig.valid=false;
| |||
sig.entry=0; sig.sl=0; sig.tp1=0; sig.tp2=0; sig.tp3=0;
| |||
sig.reason="";
| |||
| |||
MqlRates r[];
| |||
if(!GetRates(InpExecutionTF,20,r)) return false;
| |||
| |||
double c=r[1].close;
| |||
double h=r[1].high;
| |||
double l=r[1].low;
| |||
| |||
// SELL reversal: sweep buy-side then close below it.
| |||
for(int z=0;z<g_buyZoneCount;z++)
| |||
{
| |||
double level=g_buyZones[z].mid;
| |||
bool sweep=(h>level && c<level);
| |||
bool closeOK=(c<level);
| |||
bool disp=BearishDisplacement(r);
| |||
| |||
if((InpRequireSweep && !sweep) || (InpRequireClose && !closeOK)) continue;
| |||
if(InpRequireDisplace && !disp) continue;
| |||
| |||
sig.isBuy=false;
| |||
sig.entry=c;
| |||
sig.signalTime=r[1].time;
| |||
sig.reason="SELL REVERSAL: BSL SWEEP";
| |||
sig.valid=true;
| |||
break;
| |||
}
| |||
| |||
// BUY reversal: sweep sell-side then close above it.
| |||
if(!sig.valid)
| |||
{
| |||
for(int z=0;z<g_sellZoneCount;z++)
| |||
{
| |||
double level=g_sellZones[z].mid;
| |||
bool sweep=(l<level && c>level);
| |||
bool closeOK=(c>level);
| |||
bool disp=BullishDisplacement(r);
| |||
| |||
if((InpRequireSweep && !sweep) || (InpRequireClose && !closeOK)) continue;
| |||
if(InpRequireDisplace && !disp) continue;
| |||
| |||
sig.isBuy=true;
| |||
sig.entry=c;
| |||
sig.signalTime=r[1].time;
| |||
sig.reason="BUY REVERSAL: SSL SWEEP";
| |||
sig.valid=true;
| |||
break;
| |||
}
| |||
}
| |||
| |||
// Breakout fallback.
| |||
if(!sig.valid && InpAllowBreakout)
| |||
{
| |||
for(int z=0;z<g_buyZoneCount;z++)
| |||
{
| |||
double level=g_buyZones[z].mid;
| |||
if(c>level && r[2].close<=level && BullishDisplacement(r))
| |||
{
| |||
sig.isBuy=true;
| |||
sig.entry=c;
| |||
sig.signalTime=r[1].time;
| |||
sig.reason="BUY BREAKOUT: BSL CLOSE";
| |||
sig.valid=true;
| |||
break;
| |||
}
| |||
}
| |||
| |||
if(!sig.valid)
| |||
{
| |||
for(int z=0;z<g_sellZoneCount;z++)
| |||
{
| |||
double level=g_sellZones[z].mid;
| |||
if(c<level && r[2].close>=level && BearishDisplacement(r))
| |||
{
| |||
sig.isBuy=false;
| |||
sig.entry=c;
| |||
sig.signalTime=r[1].time;
| |||
sig.reason="SELL BREAKOUT: SSL CLOSE";
| |||
sig.valid=true;
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
if(!sig.valid) return false;
| |||
| |||
// M1 swing SL
| |||
if(sig.isBuy)
| |||
sig.sl=LastSwingLow(InpSwingTF,InpM1PivotStrength,2,400);
| |||
else
| |||
sig.sl=LastSwingHigh(InpSwingTF,InpM1PivotStrength,2,400);
| |||
| |||
if(sig.sl<=0) return false;
| |||
| |||
// Ensure SL is on correct side.
| |||
if(sig.isBuy && sig.sl>=sig.entry) return false;
| |||
if(!sig.isBuy && sig.sl<=sig.entry) return false;
| |||
| |||
double targets[];
| |||
int n=FindSwingTargets(sig.isBuy,sig.entry,targets,3);
| |||
| |||
// If insufficient M1 swings, use R multiples as fallback.
| |||
double risk=MathAbs(sig.entry-sig.sl);
| |||
if(risk<=SymbolInfoDouble(_Symbol,SYMBOL_POINT)*2) return false;
| |||
| |||
if(n>=1) sig.tp1=targets[0];
| |||
else sig.tp1=sig.isBuy ? sig.entry+risk : sig.entry-risk;
| |||
| |||
if(n>=2) sig.tp2=targets[1];
| |||
else sig.tp2=sig.isBuy ? sig.entry+risk*2.0 : sig.entry-risk*2.0;
| |||
| |||
if(n>=3) sig.tp3=targets[2];
| |||
else sig.tp3=sig.isBuy ? sig.entry+risk*3.0 : sig.entry-risk*3.0;
| |||
| |||
// Validate targets; fallback to R if invalid.
| |||
if(sig.isBuy)
| |||
{
| |||
if(sig.tp1<=sig.entry) sig.tp1=sig.entry+risk;
| |||
if(sig.tp2<=sig.tp1) sig.tp2=sig.entry+risk*2.0;
| |||
if(sig.tp3<=sig.tp2) sig.tp3=sig.entry+risk*3.0;
| |||
}
| |||
else
| |||
{
| |||
if(sig.tp1>=sig.entry) sig.tp1=sig.entry-risk;
| |||
if(sig.tp2>=sig.tp1) sig.tp2=sig.entry-risk*2.0;
| |||
if(sig.tp3>=sig.tp2) sig.tp3=sig.entry-risk*3.0;
| |||
}
| |||
| |||
sig.entry=NormalizePrice(sig.entry);
| |||
sig.sl=NormalizePrice(sig.sl);
| |||
sig.tp1=NormalizePrice(sig.tp1);
| |||
sig.tp2=NormalizePrice(sig.tp2);
| |||
sig.tp3=NormalizePrice(sig.tp3);
| |||
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Position tool |
| |||
//+------------------------------------------------------------------+
| |||
//+------------------------------------------------------------------+
| |||
//| Отрисовка графических инструментов + передача данных советнику |
| |||
//+------------------------------------------------------------------+
| |||
void DrawPositionTool(TradeSetup &s)
| |||
{
| |||
if(!InpShowPositionTools) return;
| |||
| |||
g_signalCounter++;
| |||
string id=IntegerToString((int)s.signalTime)+"_"+IntegerToString(g_signalCounter);
| |||
string base=InpObjectPrefix+(s.isBuy?"BUY_":"SELL_")+id;
| |||
| |||
datetime t1=s.signalTime;
| |||
datetime t2=t1+PeriodSeconds(InpExecutionTF)*InpZoneForwardBars;
| |||
| |||
color entryClr=InpEntryColor;
| |||
color slClr=InpSLColor;
| |||
color tpClr=InpTPColor;
| |||
| |||
CreateTrend(base+"_ENTRY",t1,s.entry,t2,s.entry,entryClr,STYLE_SOLID,2,false);
| |||
CreateTrend(base+"_SL",t1,s.sl,t2,s.sl,slClr,STYLE_DASH,1,false);
| |||
CreateTrend(base+"_TP1",t1,s.tp1,t2,s.tp1,tpClr,STYLE_DOT,1,false);
| |||
CreateTrend(base+"_TP2",t1,s.tp2,t2,s.tp2,tpClr,STYLE_DOT,1,false);
| |||
CreateTrend(base+"_TP3",t1,s.tp3,t2,s.tp3,tpClr,STYLE_DOT,1,false);
| |||
| |||
if(InpShowPriceText)
| |||
{
| |||
string side=s.isBuy?"BUY":"SELL";
| |||
| |||
CreateText(base+"_ENTRY_TXT",t1,s.entry,
| |||
side+" ENTRY "+PriceStr(s.entry),entryClr,9);
| |||
CreateText(base+"_SL_TXT",t1,s.sl,
| |||
"SL "+PriceStr(s.sl),slClr,9);
| |||
CreateText(base+"_TP1_TXT",t1,s.tp1,
| |||
"TP1 "+PriceStr(s.tp1),tpClr,9);
| |||
CreateText(base+"_TP2_TXT",t1,s.tp2,
| |||
"TP2 "+PriceStr(s.tp2),tpClr,9);
| |||
CreateText(base+"_TP3_TXT",t1,s.tp3,
| |||
"TP3 "+PriceStr(s.tp3),tpClr,9);
| |||
| |||
CreateText(base+"_REASON_TXT",t1,
| |||
s.isBuy?s.entry:s.entry,
| |||
side+" | "+s.reason, s.isBuy?InpBuyColor:InpSellColor,8);
| |||
}
| |||
| |||
// =================================================================
| |||
// БЛОК ЗАПИСИ ДЛЯ СОВЕТНИКА-ИСПОЛНИТЕЛЯ
| |||
// =================================================================
| |||
// Формируем уникальный префикс для текущего символа (например, "XAUUSD_SIG_")
| |||
string prefix = _Symbol + "_SIG_";
| |||
| |||
// Передаем параметры сделки в оперативную память терминала
| |||
GlobalVariableSet(prefix + "SIDE", s.isBuy ? 1.0 : 2.0); // 1 = BUY, 2 = SELL
| |||
GlobalVariableSet(prefix + "ENTRY", s.entry);
| |||
GlobalVariableSet(prefix + "SL", s.sl);
| |||
GlobalVariableSet(prefix + "TP1", s.tp1);
| |||
GlobalVariableSet(prefix + "TP2", s.tp2);
| |||
GlobalVariableSet(prefix + "TP3", s.tp3);
| |||
| |||
// Записываем время сигнала. Советник сравнивает его, чтобы не дублировать сделки
| |||
GlobalVariableSet(prefix + "TIME", (double)s.signalTime);
| |||
// =================================================================
| |||
| |||
ChartRedraw();
| |||
}
| |||
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Sweep line |
| |||
//+------------------------------------------------------------------+
| |||
void DrawSweepLine(bool buySide,double level,datetime when,string reason)
| |||
{
| |||
if(!InpShowSweepLines) return;
| |||
| |||
g_signalCounter++;
| |||
string base=InpObjectPrefix+"SWEEP_"+IntegerToString((int)when)+"_"+IntegerToString(g_signalCounter);
| |||
| |||
datetime t2=when+PeriodSeconds(InpExecutionTF)*InpZoneForwardBars;
| |||
CreateTrend(base,when,level,t2,level,InpSweepColor,STYLE_DASHDOT,2,false);
| |||
| |||
if(InpShowPriceText)
| |||
{
| |||
string txt=buySide ? "BSL SWEPT " : "SSL SWEPT ";
| |||
txt+=PriceStr(level);
| |||
CreateText(base+"_TXT",when,level,txt,InpSweepColor,8);
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Alert |
| |||
//+------------------------------------------------------------------+
| |||
void SendSignalAlert(TradeSetup &s)
| |||
{
| |||
if(!InpAlerts) return;
| |||
| |||
string side=s.isBuy?"BUY":"SELL";
| |||
string msg=StringFormat("%s %s %s\nENTRY: %s\nSL: %s\nTP1: %s\nTP2: %s\nTP3: %s\n%s",
| |||
_Symbol,TFName(InpExecutionTF),side,
| |||
PriceStr(s.entry),PriceStr(s.sl),
| |||
PriceStr(s.tp1),PriceStr(s.tp2),PriceStr(s.tp3),
| |||
s.reason);
| |||
| |||
Alert(msg);
| |||
if(InpSound) PlaySound(InpSoundFile);
| |||
if(InpPushNotification) SendNotification(msg);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Dashboard |
| |||
//+------------------------------------------------------------------+
| |||
void DrawDashboard()
| |||
{
| |||
string bg = InpObjectPrefix + "DASH_BG";
| |||
| |||
// 1. Проверяем задний фон: если его нет, создаем ОДИН раз
| |||
if(ObjectFind(0, bg) < 0)
| |||
{
| |||
ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
| |||
ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
| |||
ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, 10);
| |||
ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, 20);
| |||
ObjectSetInteger(0, bg, OBJPROP_XSIZE, 300);
| |||
ObjectSetInteger(0, bg, OBJPROP_YSIZE, 240); // Немного увеличили высоту для всех строк
| |||
ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, clrBlack);
| |||
ObjectSetInteger(0, bg, OBJPROP_COLOR, clrDimGray);
| |||
ObjectSetInteger(0, bg, OBJPROP_BACK, false);
| |||
ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
| |||
}
| |||
| |||
// 2. Формируем массив строк для корректного вывода на экран
| |||
string lines[];
| |||
ArrayResize(lines, 15);
| |||
| |||
lines[0] = "MTF LIQUIDITY + OB";
| |||
lines[1] = _Symbol + " | " + TFName(InpExecutionTF);
| |||
lines[2] = "------------------------------";
| |||
lines[3] = "PDH BSL : " + PriceStr(g_pdh);
| |||
lines[4] = "PDL SSL : " + PriceStr(g_pdl);
| |||
lines[5] = "BUY ZONES : " + IntegerToString(g_buyZoneCount);
| |||
lines[6] = "SELL ZONES : " + IntegerToString(g_sellZoneCount);
| |||
lines[7] = "MATCH DIST : " + DoubleToString(MatchDistance(), _Digits);
| |||
lines[8] = "------------------------------";
| |||
lines[9] = "STATUS : " + g_status;
| |||
lines[10] = "BUY RUN : " + (g_buyRunning ? "YES" : "NO");
| |||
lines[11] = "SELL RUN : " + (g_sellRunning ? "YES" : "NO");
| |||
lines[12] = "------------------------------";
| |||
lines[13] = "5M RESET : ACTIVE";
| |||
lines[14] = "M1 SL/TP : ACTIVE";
| |||
| |||
// Начальная координата Y для первой строчки текста и шаг смещения вниз
| |||
int startY = 28;
| |||
int stepY = 14;
| |||
| |||
// 3. Выводим каждую строку как отдельный текстовый объект
| |||
for(int i = 0; i < ArraySize(lines); i++)
| |||
{
| |||
string tx = InpObjectPrefix + "DASH_TXT_" + IntegerToString(i);
| |||
| |||
// Если объект строки не создан — создаем его
| |||
if(ObjectFind(0, tx) < 0)
| |||
{
| |||
ObjectCreate(0, tx, OBJ_LABEL, 0, 0, 0);
| |||
ObjectSetInteger(0, tx, OBJPROP_CORNER, CORNER_LEFT_UPPER);
| |||
ObjectSetInteger(0, tx, OBJPROP_XDISTANCE, 20); // Отступ слева внутри панели
| |||
ObjectSetInteger(0, tx, OBJPROP_YDISTANCE, startY + (i * stepY)); // Авто-смещение вниз
| |||
ObjectSetString(0, tx, OBJPROP_FONT, "Consolas");
| |||
ObjectSetInteger(0, tx, OBJPROP_FONTSIZE, 9);
| |||
ObjectSetInteger(0, tx, OBJPROP_COLOR, clrWhite);
| |||
ObjectSetInteger(0, tx, OBJPROP_SELECTABLE, false);
| |||
}
| |||
| |||
// Просто обновляем текст в уже созданном объекте (работает мгновенно без мигания)
| |||
ObjectSetString(0, tx, OBJPROP_TEXT, lines[i]);
| |||
}
| |||
| |||
ChartRedraw();
| |||
}
| |||
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Check running setup TP1 / SL |
| |||
//+------------------------------------------------------------------+
| |||
void UpdateRunningStates()
| |||
{
| |||
double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
| |||
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
| |||
| |||
if(g_buyRunning)
| |||
{
| |||
if(bid<=g_activeBuySL)
| |||
{
| |||
g_buyRunning=false;
| |||
g_buyTP1=false;
| |||
g_status="BUY SL HIT";
| |||
}
| |||
else if(bid>=g_activeBuyTP1)
| |||
{
| |||
g_buyTP1=true;
| |||
g_buyRunning=false;
| |||
g_status="BUY TP1 HIT";
| |||
}
| |||
}
| |||
| |||
if(g_sellRunning)
| |||
{
| |||
if(ask>=g_activeSellSL)
| |||
{
| |||
g_sellRunning=false;
| |||
g_sellTP1=false;
| |||
g_status="SELL SL HIT";
| |||
}
| |||
else if(ask<=g_activeSellTP1)
| |||
{
| |||
g_sellTP1=true;
| |||
g_sellRunning=false;
| |||
g_status="SELL TP1 HIT";
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Process signal |
| |||
//+------------------------------------------------------------------+
| |||
void ProcessNewM5Signal()
| |||
{
| |||
TradeSetup s;
| |||
if(!GetSignal(s))
| |||
{
| |||
g_status="WAITING FOR SWEEP / BREAKOUT";
| |||
return;
| |||
}
| |||
| |||
if(s.signalTime==g_lastSignalBar)
| |||
return;
| |||
| |||
g_lastSignalBar=s.signalTime;
| |||
| |||
// Draw sweep line when applicable.
| |||
MqlRates r[];
| |||
GetRates(InpExecutionTF,10,r);
| |||
| |||
if(s.isBuy)
| |||
{
| |||
// Find swept sell zone nearest the candle.
| |||
for(int z=0;z<g_sellZoneCount;z++)
| |||
{
| |||
if(r[1].low<g_sellZones[z].mid && r[1].close>g_sellZones[z].mid)
| |||
{
| |||
DrawSweepLine(false,g_sellZones[z].mid,s.signalTime,"SSL");
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
else
| |||
{
| |||
for(int z=0;z<g_buyZoneCount;z++)
| |||
{
| |||
if(r[1].high>g_buyZones[z].mid && r[1].close<g_buyZones[z].mid)
| |||
{
| |||
DrawSweepLine(true,g_buyZones[z].mid,s.signalTime,"BSL");
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
| |||
// Keep historical position tools.
| |||
DrawPositionTool(s);
| |||
SendSignalAlert(s);
| |||
| |||
// State management: opposite signal is allowed and gets its own tool.
| |||
if(s.isBuy)
| |||
{
| |||
g_buyRunning=true;
| |||
g_activeBuyEntry=s.entry;
| |||
g_activeBuySL=s.sl;
| |||
g_activeBuyTP1=s.tp1;
| |||
g_status="BUY SIGNAL";
| |||
}
| |||
else
| |||
{
| |||
g_sellRunning=true;
| |||
g_activeSellEntry=s.entry;
| |||
g_activeSellSL=s.sl;
| |||
g_activeSellTP1=s.tp1;
| |||
g_status="SELL SIGNAL";
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Initialization |
| |||
//+------------------------------------------------------------------+
| |||
int OnInit()
| |||
{
| |||
IndicatorSetString(INDICATOR_SHORTNAME,"MTF Liquidity OB Reversal");
| |||
| |||
CalculatePreviousDay();
| |||
BuildZones();
| |||
DrawZones();
| |||
DrawDashboard();
| |||
| |||
g_lastM5Bar=iTime(_Symbol,InpExecutionTF,0);
| |||
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Deinitialization |
| |||
//+------------------------------------------------------------------+
| |||
void OnDeinit(const int reason)
| |||
{
| |||
// Do NOT delete historical position tools or sweep lines.
| |||
// Delete only live dashboard/current zone objects.
| |||
DeleteObjectsByPrefix(InpObjectPrefix+"ZONE_");
| |||
DeleteObjectsByPrefix(InpObjectPrefix+"HTF_");
| |||
DeleteObjectsByPrefix(InpObjectPrefix+"DASH_");
| |||
ChartRedraw();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Main calculation |
| |||
//+------------------------------------------------------------------+
| |||
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[])
| |||
{
| |||
UpdateRunningStates();
| |||
| |||
if(IsNewExecutionBar())
| |||
{
| |||
// Recalculate steps 1-6 every new M5 candle.
| |||
CalculatePreviousDay();
| |||
BuildZones();
| |||
DrawZones();
| |||
DrawDashboard();
| |||
| |||
// Signal is based on the newly closed M5 candle.
| |||
ProcessNewM5Signal();
| |||
}
| |||
| |||
DrawDashboard();
| |||
return rates_total;
| |||
}
| |||
//+------------------------------------------------------------------+
|