426 lines
16 KiB
MQL5
426 lines
16 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| AF_Engine2_Display.mqh |
|
|
//| Project : Algo Forge (refactor total) |
|
|
//| Sumber inspirasi: SniperGold SMC Pro+ (c) Waseem Shahrukh |
|
|
//| https://www.mql5.com/en/code/75466 |
|
|
//+------------------------------------------------------------------+
|
|
//| Engine 2 - PEMBANGUN KONTEKS DISPLAY (konsumsi Engine 3) |
|
|
//| |
|
|
//| - Engine 3 (display) TIDAK menghitung sendiri. Semua data |
|
|
//| struktur/zona/level yang perlu DIGAMBAR disiapkan DI SINI |
|
|
//| (layer Engine 2), memakai helper analisis murni yang SAMA |
|
|
//| dengan agen N/C/E/P (AF_BuildSwing, AF_FindOrderBlock, dll). |
|
|
//| - AF_BuildDisplayData membaca HANYA: |
|
|
//| * Engine 1 (AFEngine1MTF) - bar tertutup (closed-bar lock) |
|
|
//| * Output Engine 2 (AFSignalOut) - bias & alasan per agen |
|
|
//| lalu mengisi AFDisplayData untuk renderer Engine 3. |
|
|
//| - Non-repaint: semua nilai dihitung dari bar tertutup Engine 1. |
|
|
//| - Konsisten dengan agen: struktur (BOS/CHoCH), swing point |
|
|
//| (HH/HL/LH/LL), OB, FVG, premium/discount, level MTF (PDH/PDL/ |
|
|
//| PWH/PWL), bias agen - setara dengan display kode asli 75466. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef AF_ENGINE2_DISPLAY_MQH
|
|
#define AF_ENGINE2_DISPLAY_MQH
|
|
|
|
#include "AF_Engine2_Aggregator.mqh"
|
|
|
|
//------------------------------------------------------------------
|
|
// Zona display (OB / FVG)
|
|
//------------------------------------------------------------------
|
|
struct AFDispZone
|
|
{
|
|
datetime t; // waktu bar zona
|
|
double top;
|
|
double bot;
|
|
bool bull; // true = zona bullish (support), false = bearish (resistance)
|
|
string tag; // "OB" / "FVG"
|
|
};
|
|
|
|
// Garis struktur (BOS / CHoCH)
|
|
struct AFDispLine
|
|
{
|
|
datetime t1;
|
|
double price;
|
|
datetime t2;
|
|
string lbl; // "BOS" / "CHoCH"
|
|
int dir; // +1 bullish, -1 bearish
|
|
};
|
|
|
|
// Swing point (dengan klasifikasi)
|
|
struct AFDispPivot
|
|
{
|
|
datetime time;
|
|
double price;
|
|
bool isHigh;
|
|
string tag; // HH/HL/LH/LL
|
|
};
|
|
|
|
//------------------------------------------------------------------
|
|
// Data display lengkap (diisi AF_BuildDisplayData; digambar Engine 3)
|
|
//------------------------------------------------------------------
|
|
struct AFDisplayData
|
|
{
|
|
string structure; // "Bullish" / "Bearish" / "Range"
|
|
string eqPos; // "Premium" / "Discount" / "Equilibrium"
|
|
int HH, HL, LH, LL;
|
|
bool eqhSwept; // buy-side liquidity (EQH) tersapu -> intent bearish
|
|
bool eqlSwept; // sell-side liquidity (EQL) tersapu -> intent bullish
|
|
double swHigh; // range tertinggi (jendela display)
|
|
double swLow; // range terendah
|
|
double rangePos; // posisi close di range [0,1]
|
|
double atr; // ATR slot display
|
|
datetime dispStart; // waktu bar tertua yang digambar
|
|
datetime dispEnd; // ujung kanan (bar tertutup terbaru + shift)
|
|
AFDispPivot pivots[]; // swing points (dengan tag)
|
|
AFDispLine structLines[]; // BOS/CHoCH
|
|
AFDispZone ob[]; // order blocks (belum mitigated)
|
|
AFDispZone fvg[]; // FVG (belum mitigated)
|
|
double pdh, pdl, pwh, pwl; // level MTF (0 = belum tersedia)
|
|
string nBias, cBias, eBias, pBias; // bias per agen (Bullish/Bearish/Range)
|
|
string nReason, cReason, eReason, pReason; // alasan per agen
|
|
|
|
void Reset()
|
|
{
|
|
structure=""; eqPos="";
|
|
HH=0; HL=0; LH=0; LL=0;
|
|
eqhSwept=false; eqlSwept=false;
|
|
swHigh=0.0; swLow=0.0; rangePos=0.5; atr=0.0;
|
|
dispStart=0; dispEnd=0;
|
|
ArrayResize(pivots,0);
|
|
ArrayResize(structLines,0);
|
|
ArrayResize(ob,0);
|
|
ArrayResize(fvg,0);
|
|
pdh=0.0; pdl=0.0; pwh=0.0; pwl=0.0;
|
|
nBias=""; cBias=""; eBias=""; pBias="";
|
|
nReason=""; cReason=""; eReason=""; pReason="";
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------
|
|
// Bantu: string bias dari arah agen
|
|
//------------------------------------------------------------------
|
|
string AF_DirBias(int dir)
|
|
{
|
|
return (dir>0)? "Bullish" : (dir<0)? "Bearish" : "Range";
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Klasifikasi swing point menjadi HH/HL/LH/LL (urutan tertua->terbaru)
|
|
// tags[]: [0..nHigh-1] = highs, [nHigh..nHigh+nLow-1] = lows
|
|
//------------------------------------------------------------------
|
|
void AF_ClassifyPivots(const AFSwingData &sd,int &HH,int &HL,int &LH,int &LL,string &tags[])
|
|
{
|
|
HH=0; HL=0; LH=0; LL=0;
|
|
double lastH=0.0, lastL=0.0;
|
|
bool haveH=false, haveL=false;
|
|
for(int i=sd.nHigh-1; i>=0; i--)
|
|
{
|
|
string tag;
|
|
if(!haveH || sd.highs[i].price>lastH) { tag="HH"; HH++; }
|
|
else { tag="LH"; LH++; }
|
|
lastH=sd.highs[i].price;
|
|
haveH=true;
|
|
tags[i]=tag;
|
|
}
|
|
for(int i=sd.nLow-1; i>=0; i--)
|
|
{
|
|
string tag;
|
|
if(!haveL || sd.lows[i].price>lastL) { tag="HL"; HL++; }
|
|
else { tag="LL"; LL++; }
|
|
lastL=sd.lows[i].price;
|
|
haveL=true;
|
|
tags[sd.nHigh+i]=tag;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Garis struktur BOS/CHoCH dari pivot fractal (kronologis)
|
|
//------------------------------------------------------------------
|
|
void AF_BuildStructLines(const AFEngine1MTF &e1,int slot,AFDispLine &lines[])
|
|
{
|
|
ArrayResize(lines,0);
|
|
if(!e1.IsReady(slot)) return;
|
|
int cnt=e1.Count(slot);
|
|
if(cnt<5) return;
|
|
double upTarget=DBL_MAX, dnTarget=-DBL_MAX;
|
|
int upBar=-1, dnBar=-1;
|
|
int trend=0;
|
|
// kronologis: i = cnt-3 (tertua) -> 2 (terbaru), index 0 = bar terbaru
|
|
for(int i=cnt-3; i>=2; i--)
|
|
{
|
|
double h=e1.High(slot,i);
|
|
double l=e1.Low(slot,i);
|
|
if(h>e1.High(slot,i-1) && h>e1.High(slot,i-2) &&
|
|
h>e1.High(slot,i+1) && h>e1.High(slot,i+2))
|
|
{ upTarget=h; upBar=i; }
|
|
if(l<e1.Low(slot,i-1) && l<e1.Low(slot,i-2) &&
|
|
l<e1.Low(slot,i+1) && l<e1.Low(slot,i+2))
|
|
{ dnTarget=l; dnBar=i; }
|
|
// bullish break
|
|
if(upBar>=0 && upBar!=i && e1.Close(slot,i)>upTarget)
|
|
{
|
|
if(ArraySize(lines)<AF_E3_MAX_LINES)
|
|
{
|
|
int n=ArraySize(lines);
|
|
ArrayResize(lines,n+1);
|
|
lines[n].t1=e1.Time(slot,upBar);
|
|
lines[n].price=upTarget;
|
|
lines[n].t2=e1.Time(slot,i);
|
|
lines[n].lbl=(trend<0)? "CHoCH" : "BOS";
|
|
lines[n].dir=1;
|
|
}
|
|
trend=1;
|
|
upTarget=DBL_MAX;
|
|
upBar=-1;
|
|
}
|
|
// bearish break
|
|
if(dnBar>=0 && dnBar!=i && e1.Close(slot,i)<dnTarget)
|
|
{
|
|
if(ArraySize(lines)<AF_E3_MAX_LINES)
|
|
{
|
|
int n=ArraySize(lines);
|
|
ArrayResize(lines,n+1);
|
|
lines[n].t1=e1.Time(slot,dnBar);
|
|
lines[n].price=dnTarget;
|
|
lines[n].t2=e1.Time(slot,i);
|
|
lines[n].lbl=(trend>0)? "CHoCH" : "BOS";
|
|
lines[n].dir=-1;
|
|
}
|
|
trend=-1;
|
|
dnTarget=-DBL_MAX;
|
|
dnBar=-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Kumpulkan Order Block (bar lawan sebelum move kuat), tanpa MIT
|
|
//------------------------------------------------------------------
|
|
void AF_CollectOBs(const AFEngine1MTF &e1,int slot,int maxN,double atr,AFDispZone &zones[])
|
|
{
|
|
ArrayResize(zones,0);
|
|
if(!e1.IsReady(slot) || maxN<=0 || atr<=0.0) return;
|
|
int cnt=e1.Count(slot);
|
|
if(cnt<5) return;
|
|
double avg=AF_AvgBody(e1,slot,AF_E2_LOOKBACK_AVG);
|
|
if(avg<=0.0) return;
|
|
int maxIdx=MathMin(cnt-3,AF_E3_DISP_BARS);
|
|
for(int i=0; i<maxIdx; i++) // i = bar "move kuat" (baru -> lama)
|
|
{
|
|
int obBar=i+1; // bar lawan tepat sebelum move
|
|
if(obBar>cnt-2) continue;
|
|
double moveBody=MathAbs(e1.Close(slot,i)-e1.Open(slot,i));
|
|
if(moveBody<AF_E3_MOVE_BODY*avg) continue;
|
|
bool moveUp=(e1.Close(slot,i)>e1.Open(slot,i));
|
|
bool prevBear=(e1.Close(slot,obBar)<e1.Open(slot,obBar));
|
|
bool prevBull=(e1.Close(slot,obBar)>e1.Open(slot,obBar));
|
|
if(!prevBear && !prevBull) continue;
|
|
bool bull;
|
|
if(moveUp && prevBear) bull=true;
|
|
else if(!moveUp && prevBull) bull=false;
|
|
else continue;
|
|
double top=e1.High(slot,obBar);
|
|
double bot=e1.Low(slot,obBar);
|
|
if((top-bot)<AF_E3_OB_MIN_ATR*atr) continue;
|
|
// MIT: harga pernah menutup menembus zona setelahnya
|
|
bool mit=false;
|
|
for(int b=obBar-1; b>=0; b--)
|
|
{
|
|
if(bull && e1.Close(slot,b)<bot) { mit=true; break; }
|
|
if(!bull && e1.Close(slot,b)>top) { mit=true; break; }
|
|
}
|
|
if(mit) continue;
|
|
// dedupe sederhana: zona hampir sama sudah ada
|
|
int nz=ArraySize(zones);
|
|
bool dup=false;
|
|
for(int k=0; k<nz; k++)
|
|
{
|
|
if(MathAbs(zones[k].top-top)<0.5*atr &&
|
|
MathAbs(zones[k].bot-bot)<0.5*atr) { dup=true; break; }
|
|
}
|
|
if(dup) continue;
|
|
if(nz>=maxN) break;
|
|
ArrayResize(zones,nz+1);
|
|
zones[nz].t=e1.Time(slot,obBar);
|
|
zones[nz].top=top;
|
|
zones[nz].bot=bot;
|
|
zones[nz].bull=bull;
|
|
zones[nz].tag="OB";
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Kumpulkan FVG (imbalance) terbaru, tanpa MIT
|
|
//------------------------------------------------------------------
|
|
void AF_CollectFVG(const AFEngine1MTF &e1,int slot,int maxN,double atr,AFDispZone &zones[])
|
|
{
|
|
ArrayResize(zones,0);
|
|
if(!e1.IsReady(slot) || maxN<=0 || atr<=0.0) return;
|
|
int cnt=e1.Count(slot);
|
|
if(cnt<3) return;
|
|
int maxIdx=MathMin(cnt-3,AF_E3_DISP_BARS);
|
|
for(int i=0; i<=maxIdx; i++)
|
|
{
|
|
double lo0=e1.Low(slot,i);
|
|
double hi2=e1.High(slot,i+2);
|
|
double hi0=e1.High(slot,i);
|
|
double lo2=e1.Low(slot,i+2);
|
|
double top=0.0, bot=0.0;
|
|
bool bull=false, found=false;
|
|
if(lo0>hi2) { bull=true; top=lo0; bot=hi2; found=true; }
|
|
else if(hi0<lo2) { bull=false; top=lo2; bot=hi0; found=true; }
|
|
if(!found) continue;
|
|
if((top-bot)<AF_E3_FVG_MIN_ATR*atr) continue;
|
|
bool mit=false;
|
|
for(int b=i-1; b>=0; b--)
|
|
{
|
|
if(bull && e1.Low(slot,b)<=bot) { mit=true; break; } // wick full-fill (S-9)
|
|
if(!bull && e1.High(slot,b)>=top) { mit=true; break; } // wick full-fill (S-9)
|
|
}
|
|
if(mit) continue;
|
|
int nz=ArraySize(zones);
|
|
if(nz>=maxN) break;
|
|
ArrayResize(zones,nz+1);
|
|
zones[nz].t=e1.Time(slot,i);
|
|
zones[nz].top=top;
|
|
zones[nz].bot=bot;
|
|
zones[nz].bull=bull;
|
|
zones[nz].tag="FVG";
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Level MTF: previous day/week high/low dari slot D1/W1 Engine 1
|
|
//------------------------------------------------------------------
|
|
void AF_MTFLevels(const AFEngine1MTF &e1,int sD1,int sW1,
|
|
double &pdh,double &pdl,double &pwh,double &pwl)
|
|
{
|
|
pdh=0.0; pdl=0.0; pwh=0.0; pwl=0.0;
|
|
if(e1.IsReady(sD1) && e1.Count(sD1)>=2)
|
|
{
|
|
pdh=e1.High(sD1,1);
|
|
pdl=e1.Low(sD1,1);
|
|
}
|
|
if(e1.IsReady(sW1) && e1.Count(sW1)>=2)
|
|
{
|
|
pwh=e1.High(sW1,1);
|
|
pwl=e1.Low(sW1,1);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Pembangun data display (SELURUH komputasi untuk Engine 3 di sini)
|
|
//------------------------------------------------------------------
|
|
void AF_BuildDisplayData(const AFEngine1MTF &e1,
|
|
int sN,int sC,int sE,int sP,int sDisp,int sD1,int sW1,
|
|
int maxOB,int maxFVG,
|
|
const AFSignalOut &oN,const AFSignalOut &oC,
|
|
const AFSignalOut &oE,const AFSignalOut &oP,
|
|
AFDisplayData &dd)
|
|
{
|
|
dd.Reset();
|
|
|
|
// bias & alasan agen (dari output Engine 2 - tidak dihitung ulang)
|
|
dd.nBias=AF_DirBias(oN.dir);
|
|
dd.cBias=AF_DirBias(oC.dir);
|
|
dd.eBias=AF_DirBias(oE.dir);
|
|
dd.pBias=AF_DirBias(oP.dir);
|
|
dd.nReason=oN.reason;
|
|
dd.cReason=oC.reason;
|
|
dd.eReason=oE.reason;
|
|
dd.pReason=oP.reason;
|
|
|
|
if(!e1.IsReady(sDisp)) return;
|
|
int cnt=e1.Count(sDisp);
|
|
if(cnt<5) return;
|
|
|
|
dd.atr=e1.ATR(sDisp,14);
|
|
|
|
// rentang & posisi close (premium/discount)
|
|
double rmin=0.0, rmax=0.0, pos=0.5;
|
|
AF_RangeStat(e1,sDisp,AF_E3_DISP_BARS,rmin,rmax,pos);
|
|
dd.swHigh=rmax;
|
|
dd.swLow=rmin;
|
|
dd.rangePos=pos;
|
|
dd.eqPos=(pos<0.38)? "Discount" : (pos>0.62)? "Premium" : "Equilibrium";
|
|
|
|
// struktur (tren) dari pivot fractal
|
|
AFSwingData sd;
|
|
AF_BuildSwing(e1,sDisp,sd,AF_E3_DISP_BARS);
|
|
int trend=0;
|
|
double clarity=0.0;
|
|
AF_TrendFromSwing(sd,trend,clarity);
|
|
dd.structure=(trend>0)? "Bullish" : (trend<0)? "Bearish" : "Range";
|
|
|
|
// klasifikasi HH/HL/LH/LL + daftar swing point
|
|
string tags[];
|
|
ArrayResize(tags,2*AF_E2_MAX_PIVOTS);
|
|
AF_ClassifyPivots(sd,dd.HH,dd.HL,dd.LH,dd.LL,tags);
|
|
int nP=MathMin(sd.nHigh+sd.nLow,AF_E3_MAX_PIVOTS);
|
|
ArrayResize(dd.pivots,nP);
|
|
int k=0;
|
|
for(int i=0; i<sd.nHigh && k<nP; i++,k++)
|
|
{
|
|
dd.pivots[k].time=sd.highs[i].time;
|
|
dd.pivots[k].price=sd.highs[i].price;
|
|
dd.pivots[k].isHigh=true;
|
|
dd.pivots[k].tag=tags[i];
|
|
}
|
|
for(int i=0; i<sd.nLow && k<nP; i++,k++)
|
|
{
|
|
dd.pivots[k].time=sd.lows[i].time;
|
|
dd.pivots[k].price=sd.lows[i].price;
|
|
dd.pivots[k].isHigh=false;
|
|
dd.pivots[k].tag=tags[sd.nHigh+i];
|
|
}
|
|
|
|
// likuiditas tersapu (EQH/EQL) - reuse detektor sweep Engine 2
|
|
int sweep=AF_DetectSweep(e1,sDisp,sd,AF_E2_SWEEP_LOOKBACK);
|
|
dd.eqlSwept=(sweep>0);
|
|
dd.eqhSwept=(sweep<0);
|
|
|
|
// garis struktur BOS/CHoCH
|
|
AF_BuildStructLines(e1,sDisp,dd.structLines);
|
|
|
|
// zona OB & FVG
|
|
AF_CollectOBs(e1,sDisp,MathMax(1,maxOB),dd.atr,dd.ob);
|
|
AF_CollectFVG(e1,sDisp,MathMax(1,maxFVG),dd.atr,dd.fvg);
|
|
|
|
// level MTF
|
|
AF_MTFLevels(e1,sD1,sW1,dd.pdh,dd.pdl,dd.pwh,dd.pwl);
|
|
|
|
// jendela waktu display
|
|
int lastIdx=MathMin(cnt-1,AF_E3_DISP_BARS);
|
|
dd.dispStart=e1.Time(sDisp,lastIdx);
|
|
int sec=AFEngine1MTF::PeriodSecondsOf(e1.TfOf(sDisp));
|
|
if(sec<=0) sec=3600;
|
|
dd.dispEnd=e1.Time(sDisp,0)+(datetime)MathMax(sec*4,3600);
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Pembanding data display (untuk uji non-repaint)
|
|
//------------------------------------------------------------------
|
|
bool AF_SameDisplayData(const AFDisplayData &a,const AFDisplayData &b)
|
|
{
|
|
if(a.structure!=b.structure) return false;
|
|
if(a.eqPos!=b.eqPos) return false;
|
|
if(a.HH!=b.HH || a.HL!=b.HL || a.LH!=b.LH || a.LL!=b.LL) return false;
|
|
if(a.eqhSwept!=b.eqhSwept || a.eqlSwept!=b.eqlSwept) return false;
|
|
if(MathAbs(a.swHigh-b.swHigh)>1e-12) return false;
|
|
if(MathAbs(a.swLow-b.swLow)>1e-12) return false;
|
|
if(MathAbs(a.rangePos-b.rangePos)>1e-12) return false;
|
|
if(MathAbs(a.atr-b.atr)>1e-12) return false;
|
|
if(a.dispStart!=b.dispStart || a.dispEnd!=b.dispEnd) return false;
|
|
if(ArraySize(a.pivots)!=ArraySize(b.pivots)) return false;
|
|
if(ArraySize(a.structLines)!=ArraySize(b.structLines)) return false;
|
|
if(ArraySize(a.ob)!=ArraySize(b.ob)) return false;
|
|
if(ArraySize(a.fvg)!=ArraySize(b.fvg)) return false;
|
|
if(MathAbs(a.pdh-b.pdh)>1e-12 || MathAbs(a.pdl-b.pdl)>1e-12) return false;
|
|
if(MathAbs(a.pwh-b.pwh)>1e-12 || MathAbs(a.pwl-b.pwl)>1e-12) return false;
|
|
if(a.nBias!=b.nBias || a.cBias!=b.cBias || a.eBias!=b.eBias || a.pBias!=b.pBias) return false;
|
|
if(a.nReason!=b.nReason || a.cReason!=b.cReason) return false;
|
|
if(a.eReason!=b.eReason || a.pReason!=b.pReason) return false;
|
|
return true;
|
|
}
|
|
|
|
#endif // AF_ENGINE2_DISPLAY_MQH
|