SniperGold_ML/MQL5/Include/AlgoForge/AF_Engine2_Display.mqh

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