2026-08-21 10:28:46 +07:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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);
|
2026-08-22 22:32:56 +07:00 | | | for(int i=0; i<=maxIdx; i++)
|
2026-08-21 10:28:46 +07:00 | | | {
|
| | | 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--)
|
| | | {
|
2026-08-22 22:32:56 +07:00 | | | 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)
|
2026-08-21 10:28:46 +07:00 | | | }
|
| | | 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
|