//+------------------------------------------------------------------+ //| AF_Engine2_Agents.mqh | //| Project : Algo Forge (refactor total) | //| Sumber inspirasi: SniperGold SMC Pro+ (c) Waseem Shahrukh | //| https://www.mql5.com/en/code/75466 | //+------------------------------------------------------------------+ //| Engine 2 - AGEN SINYAL INDEPENDEN (N/C/E/P) + FUZZY LOGIC | //| | //| PRINSIP KUNCI: | //| 1. Tiap agen (Narrative/Context/Entry/PriceAction) adalah | //| komponen INDEPENDEN: hanya membaca Engine 1 (AFEngine1MTF) | //| melalui SATU slot (timeframe) miliknya sendiri. | //| 2. Agen TIDAK saling tahu: tidak ada pemanggilan/state antar | //| agen; tidak ada anggota statis bersama; semua metode murni | //| fungsi dari data bar tertutup Engine 1 (stateless). | //| 3. Fuzzy logic berbobot dinamis: tiap agen memakai fungsi | //| keanggotaan (membership) + aturan berbobot; bobot berubah | //| adaptif terhadap kondisi pasar yang diukur dari data agen | //| itu sendiri (kekuatan tren, volatilitas, ranging, dll). | //| 4. Non-repaint: semua input adalah bar TERTUTUP dari Engine 1 | //| (closed-bar lock dijamin Engine 1). | //| 5. Output per agen (AFSignalOut) dikonsumsi oleh AGREGATOR | //| TERPISAH (AF_Engine2_Aggregator.mqh) - BUKAN antar-agen. | //+------------------------------------------------------------------+ #ifndef AF_ENGINE2_AGENTS_MQH #define AF_ENGINE2_AGENTS_MQH #include "AF_Defines.mqh" #include "AF_Engine1_MTFData.mqh" //------------------------------------------------------------------ // Output standar satu agen (untuk agregator & display Engine 3) //------------------------------------------------------------------ struct AFSignalOut { string name; // nama agen string tfName; // timeframe yang dipakai (string, untuk display) double buy; // dukungan fuzzy BUY [0,1] double sell; // dukungan fuzzy SELL [0,1] double bias; // buy - sell [-1,1] double confidence; // [0,1] kekuatan sinyal agen int dir; // +1 buy / -1 sell / 0 wait string reason; // alasan ringkas (untuk display) }; void AF_SignalInit(AFSignalOut &out,const string name,const string tfName) { out.name=name; out.tfName=tfName; out.buy=0.0; out.sell=0.0; out.bias=0.0; out.confidence=0.0; out.dir=0; out.reason=""; } //------------------------------------------------------------------ // Helper fuzzy (murni, tanpa state - aman dipakai semua agen) //------------------------------------------------------------------ double AF_Clamp01(double v) { return (v<0.0)? 0.0 : (v>1.0)? 1.0 : v; } // Fungsi keanggotaan segitiga (a=c) return 0.0; if(x==b) return 1.0; double lo=(b>a)? (x-a)/(b-a) : 1.0; double hi=(c>b)? (c-x)/(c-b) : 1.0; if(x=d) return 0.0; if(x>=b && x<=c) return 1.0; double lo=(b>a)? (x-a)/(b-a) : 1.0; double hi=(d>c)? (d-x)/(d-c) : 1.0; if(x0.0)? wTot : 1.0; out.buy = AF_Clamp01(buyAcc/denom); out.sell = AF_Clamp01(sellAcc/denom); out.bias = out.buy-out.sell; out.confidence= MathMax(out.buy,out.sell); out.dir = (out.bias> AF_E2_DIR_TOL)? 1 : (out.bias< -AF_E2_DIR_TOL)? -1 : 0; out.reason = ""; } }; //------------------------------------------------------------------ // Statistik bar (murni, tanpa state) //------------------------------------------------------------------ double AF_AvgBody(const AFEngine1MTF &e1,int slot,int n) { if(!e1.IsReady(slot)) return 0.0; int m=MathMin(n,e1.Count(slot)); if(m<=0) return 0.0; double s=0.0; for(int i=0;irmax) rmax=h; if(lrmin) pos=AF_Clamp01((e1.Close(slot,0)-rmin)/(rmax-rmin)); else pos=0.5; } //------------------------------------------------------------------ // Analisis struktur (pivot/trend/CHoCH/BOS/sweep) - murni, tanpa state //------------------------------------------------------------------ struct AFPivot { datetime time; double price; int idx; // index bar di cache Engine 1 }; struct AFSwingData { AFPivot highs[AF_E2_MAX_PIVOTS]; AFPivot lows [AF_E2_MAX_PIVOTS]; int nHigh; int nLow; void Reset(){ nHigh=0; nLow=0; } }; // Pivot fractal 2-kiri/2-kanan; dikumpulkan dari bar TERBARU (idx kecil). void AF_BuildSwing(const AFEngine1MTF &e1,int slot,AFSwingData &sd,int lookback) { sd.Reset(); if(!e1.IsReady(slot)) return; int cnt=e1.Count(slot); if(cnt<5) return; int maxIdx=MathMin(cnt-3,(lookback>0)? lookback : cnt); for(int i=2;i<=maxIdx;i++) { double h=e1.High(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)) { if(sd.nHighsd.highs[i+1].price) up++; else dn++; pairs++; } for(int i=0;i+1sd.lows[i+1].price) up++; else dn++; pairs++; } if(pairs<=0) return; clarity=(double)MathMax(up,dn)/(double)pairs; trend=(up>dn)? 1 : (dn>up)? -1 : 0; } // CHoCH: +1 bullish (break di atas LH pada struktur turun), -1 bearish, 0 none int AF_DetectChoch(const AFEngine1MTF &e1,int slot,const AFSwingData &sd,int prevTrend) { if(!e1.IsReady(slot)) return 0; if(sd.nHigh<1 || sd.nLow<1) return 0; double close=e1.Close(slot,0); if(prevTrend<0 && close>sd.highs[0].price) return 1; if(prevTrend>0 && close0 && sd.nHigh>=1 && close>sd.highs[0].price) return 1; if(trend<0 && sd.nLow>=1 && close=2)? MathMin(sd.lows[0].price,sd.lows[1].price) : sd.lows[0].price; double levelHigh=(sd.nHigh>=2)? MathMax(sd.highs[0].price,sd.highs[1].price): sd.highs[0].price; int bestBull=-1, bestBear=-1; for(int i=0;ilevelLow) bestBull=i; if(bestBear<0 && e1.High(slot,i)>levelHigh && e1.Close(slot,i)=0 && (bestBear<0 || bestBull<=bestBear)) return 1; if(bestBear>=0) return -1; return 0; } // Displacement: body bar terbaru >> rata-rata body (move kuat) int AF_DetectDisplacement(const AFEngine1MTF &e1,int slot,int avgN) { if(!e1.IsReady(slot)) return 0; double avg=AF_AvgBody(e1,slot,avgN); if(avg<=0.0) return 0; double body=MathAbs(e1.Close(slot,0)-e1.Open(slot,0)); if(body<1.6*avg) return 0; return (e1.Close(slot,0)>e1.Open(slot,0))? 1 : -1; } //------------------------------------------------------------------ // F1 EVENT CONTRACT — materialization helpers (P3-S.11 semantics) // The stateless detectors (AF_DetectSweep / AF_DetectChoch) return only // the direction; these helpers expose the ONSET bar that the detector // already computes internally, so the F3 setup layer can consume F1 // EVENTS {onset, dir, valid_until} instead of raw detector state. // Geometry is UNCHANGED — same scans, same conditions; only the onset // is reported. (P3-S.7 S-ST: stateless per-bar, no persistent state.) //------------------------------------------------------------------ // Sweep EVENT: same scan as AF_DetectSweep; returns dir (+1/-1/0) and // sets onsetBar = reversed cache index of the newest sweep bar (-1 none). int AF_DetectSweepEvent(const AFEngine1MTF &e1,int slot,const AFSwingData &sd, int lookback,int &onsetBar) { onsetBar=-1; if(!e1.IsReady(slot)) return 0; if(sd.nLow<1 || sd.nHigh<1) return 0; int cnt=e1.Count(slot); int n=MathMin(lookback,cnt-1); if(n<1) return 0; double levelLow =(sd.nLow>=2)? MathMin(sd.lows[0].price,sd.lows[1].price) : sd.lows[0].price; double levelHigh=(sd.nHigh>=2)? MathMax(sd.highs[0].price,sd.highs[1].price): sd.highs[0].price; int bestBull=-1, bestBear=-1; for(int i=0;ilevelLow) bestBull=i; if(bestBear<0 && e1.High(slot,i)>levelHigh && e1.Close(slot,i)=0 && (bestBear<0 || bestBull<=bestBear)) { onsetBar=bestBull; return 1; } if(bestBear>=0) { onsetBar=bestBear; return -1; } return 0; } // CHoCH EVENT: same condition as AF_DetectChoch; onset = the CURRENT // decision bar (bar 0) when the break is close-confirmed on the newest // swing pivot (per-bar stateless, P3-S.7 S-ST). int AF_DetectChochEvent(const AFEngine1MTF &e1,int slot,const AFSwingData &sd, int prevTrend,int &onsetBar) { onsetBar=-1; if(!e1.IsReady(slot)) return 0; if(sd.nHigh<1 || sd.nLow<1) return 0; double close=e1.Close(slot,0); if(prevTrend<0 && close>sd.highs[0].price) { onsetBar=0; return 1; } if(prevTrend>0 && close=top // on any later bar j (j < formation bar in reversed indexing). Partial fill = // any later bar whose range overlaps the zone [bot,top]. void AF_FVGZoneState(const AFEngine1MTF &e1,int slot,int c3,int dir, double top,double bot,AFZoneState &zs) { zs.bar=c3; zs.dir=dir; zs.top=top; zs.bot=bot; zs.mit_state=AF_ZONE_UNMITIGATED; zs.partial_filled=false; zs.invalidated=false; if(dir==0) return; for(int j=c3-1;j>=0;j--) { double lo=e1.Low(slot,j); double hi=e1.High(slot,j); bool full=(dir>0)? (lo<=bot) : (hi>=top); if(full) { zs.mit_state=AF_ZONE_FULLY_MITIGATED; zs.partial_filled=true; // the full-fill bar itself overlaps the zone zs.invalidated=true; // zone-level terminal (frozen §M) return; } if(hi>=bot-1e-12 && lo<=top+1e-12) zs.partial_filled=true; } } // OB zone state (S-9): close-through full fill — bull Close(j)top on any later bar j (including the move candle, matching // the display collector). Partial fill = any bar strictly AFTER the move // candle (j < obBar-1 in reversed indexing) overlapping the zone. void AF_OBZoneState(const AFEngine1MTF &e1,int slot,int obBar,int dir, double top,double bot,AFZoneState &zs) { zs.bar=obBar; zs.dir=dir; zs.top=top; zs.bot=bot; zs.mit_state=AF_ZONE_UNMITIGATED; zs.partial_filled=false; zs.invalidated=false; if(dir==0) return; for(int j=obBar-1;j>=0;j--) // includes the move candle (j=obBar-1) { double cl=e1.Close(slot,j); bool full=(dir>0)? (cltop); if(full) { zs.mit_state=AF_ZONE_FULLY_MITIGATED; zs.partial_filled=true; zs.invalidated=true; return; } } for(int j=obBar-2;j>=0;j--) // strictly after the move candle (re-entry) { double lo=e1.Low(slot,j); double hi=e1.High(slot,j); if(hi>=bot-1e-12 && lo<=top+1e-12) { zs.partial_filled=true; break; } } } // Order Block terakhir: bar lawan warna sebelum move kuat. // return +1 bullish OB, -1 bearish OB, 0 none; zona via zoneHi/zoneLo. // P3-S.12 F2: hanya zona AKTIF (belum full-mitigasi close-through) yang // dikembalikan; zona yang sudah dimitigasi dilewati (S-9, BUG-P3S5-001). int AF_FindOrderBlock(const AFEngine1MTF &e1,int slot,int avgN,double &zoneHi,double &zoneLo) { zoneHi=0.0; zoneLo=0.0; if(!e1.IsReady(slot)) return 0; int cnt=e1.Count(slot); if(cnte1.Open(slot,i-1); int dir=0; if(upMove) { if(e1.Close(slot,i)e1.Open(slot,i)) dir=-1; } if(dir==0) continue; double top=e1.High(slot,i); double bot=e1.Low(slot,i); AFZoneState zs; AF_OBZoneState(e1,slot,i,dir,top,bot,zs); if(!zs.IsActive()) continue; // skip close-through mitigated (S-9) zoneHi=top; zoneLo=bot; return dir; } return 0; } // FVG (imbalance) terakhir. +1 bullish, -1 bearish, 0 none; zona via out. // P3-S.12 F2: i=0 (bar tertutup terbaru ELIGIBLE sebagai C3, S-6 — tanpa // lag 1 bar, tanpa future candle, BUG-P3S4-002); hanya zona AKTIF yang // dikembalikan (wick full-fill = mitigated, dilewati, S-9, BUG-P3S4-001). int AF_FindFVG(const AFEngine1MTF &e1,int slot,int lookback,double &zoneHi,double &zoneLo) { zoneHi=0.0; zoneLo=0.0; if(!e1.IsReady(slot)) return 0; int cnt=e1.Count(slot); if(cnt<3) return 0; int maxIdx=MathMin(cnt-3,(lookback>0)? lookback : cnt); for(int i=0;i<=maxIdx;i++) { double l0=e1.Low(slot,i); double h2=e1.High(slot,i+2); if(l0>h2) // bullish FVG: gap antara candle i dan i+2 { double bot=h2; double top=l0; AFZoneState zs; AF_FVGZoneState(e1,slot,i,1,top,bot,zs); if(!zs.IsActive()) continue; // skip wick-full-filled (S-9) zoneLo=bot; zoneHi=top; return 1; } double h0=e1.High(slot,i); double l2=e1.Low(slot,i+2); if(h0o0), b1=(c1>o1); double body0=MathAbs(c0-o0), rng0=h0-l0; int best=0; double bestStr=0.0; // 1. engulfing (terkuat) if(b0 && !b1 && c0>=o1 && o0<=c1) { best=1; bestStr=1.0; } else if(!b0 && b1 && c0<=o1 && o0>=c1) { best=-1; bestStr=1.0; } // 2. pin bar / hammer / shooting star if(rng0>0.0) { double lowerWick=MathMin(o0,c0)-l0; double upperWick=h0-MathMax(o0,c0); if(body0>0.0 && lowerWick>2.0*body0 && upperWick<0.5*body0 && bestStr<0.85) { best=1; bestStr=0.85; } if(body0>0.0 && upperWick>2.0*body0 && lowerWick<0.5*body0 && bestStr<0.85) { best=-1; bestStr=0.85; } } // 3. inside bar -> kelanjutan arah bar sebelumnya if(h0<=h1+1e-12 && l0>=l1-1e-12) { if(b1 && bestStr<0.6) { best=1; bestStr=0.6; } if(!b1 && bestStr<0.6) { best=-1; bestStr=0.6; } } // 4. momentum 2 bar searah if(b0 && b1 && bestStr<0.7) { best=1; bestStr=0.7; } if(!b0 && !b1 && bestStr<0.7) { best=-1; bestStr=0.7; } // 5. posisi close di range bar terakhir if(rng0>0.0) { double pos=(c0-l0)/rng0; if(pos>0.70 && bestStr<0.5) { best=1; bestStr=0.5; } if(pos<0.30 && bestStr<0.5) { best=-1; bestStr=0.5; } } code=best; strength=bestStr; return best; } // Nama TF ringkas untuk display (hapus prefix "PERIOD_") string AF_E2_TfName(ENUM_TIMEFRAMES tf) { string s=EnumToString(tf); int p=StringFind(s,"PERIOD_"); return (p==0)? StringSubstr(s,7) : s; } //+------------------------------------------------------------------+ //| AGEN S1 - NARRATIVE (N): "Arah pasar apa?" | //| Menilai: struktur HH/HL/LL/LH, CHoCH/MSS, BOS, likuiditas | //| tersapu, premium/discount. | //+------------------------------------------------------------------+ class AFAgentNarrative { public: void Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const; }; void AFAgentNarrative::Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const { string tfName=(e1.IsReady(slot))? AF_E2_TfName(e1.TfOf(slot)) : "?"; AF_SignalInit(out,"Narrative",tfName); if(!e1.IsReady(slot)) { out.reason="engine1 belum siap"; return; } if(e1.Count(slot)0)? 0.35+0.65*clarity : 0.0; double mBear=(trend<0)? 0.35+0.65*clarity : 0.0; double mDisc=AF_MF_Trap(pos,0.0,0.0,0.30,0.45); // zona diskon (beli) double mPrem=AF_MF_Trap(pos,0.55,0.70,1.0,1.0); // zona premium (jual) // bobot dinamis: tren jelas -> struktur dominan; flat -> zona/likuiditas double wStruct=0.40*(0.5+0.5*clarity); double wLiq =0.35; double wZone =0.25*(1.5-0.5*clarity); double wSum =wStruct+wLiq+wZone; wStruct/=wSum; wLiq/=wSum; wZone/=wSum; AFFuzzyEval fz; fz.Reset(); fz.Rule(true, mBull, wStruct); fz.Rule(false, mBear, wStruct); fz.Rule(true, (sweep>0)? 1.0 : 0.0, wLiq); fz.Rule(false,(sweep<0)? 1.0 : 0.0, wLiq); fz.Rule(true, mDisc, wZone); fz.Rule(false, mPrem, wZone); if(choch>0) fz.Rule(true, 0.8, wStruct*0.5); if(choch<0) fz.Rule(false, 0.8, wStruct*0.5); if(bos>0 && trend>0) fz.Rule(true, 0.7, wStruct*0.3); if(bos<0 && trend<0) fz.Rule(false, 0.7, wStruct*0.3); fz.Finalize(out,out.name,out.tfName); // reason out.reason=(trend>0)? "tren naik" : (trend<0)? "tren turun" : "sideways"; if(choch>0) out.reason+=" +CHoCH naik"; if(choch<0) out.reason+=" -CHoCH turun"; if(bos>0) out.reason+=" +BOS"; if(bos<0) out.reason+=" -BOS"; if(sweep>0) out.reason+=" +sweep likuiditas bawah"; if(sweep<0) out.reason+=" -sweep likuiditas atas"; if(pos<0.38) out.reason+=" +zona diskon"; if(pos>0.62) out.reason+=" -zona premium"; } //+------------------------------------------------------------------+ //| AGEN S2 - CONTEXT (C): "Di zona mana harga sekarang?" | //| Menilai: OB, FVG, S/R (supply/demand), premium/discount. | //+------------------------------------------------------------------+ class AFAgentContext { public: void Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const; }; void AFAgentContext::Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const { string tfName=(e1.IsReady(slot))? AF_E2_TfName(e1.TfOf(slot)) : "?"; AF_SignalInit(out,"Context",tfName); if(!e1.IsReady(slot)) { out.reason="engine1 belum siap"; return; } if(e1.Count(slot)=0.0 && d=0.0 && d0) mInObBull=AF_MF_Trap(close,obLo-0.3*atr,obLo,obHi,obHi+0.3*atr); if(ob<0) mInObBear=AF_MF_Trap(close,obLo-0.3*atr,obLo,obHi,obHi+0.3*atr); double mInFvBull=0.0, mInFvBear=0.0; if(fv>0) mInFvBull=AF_MF_Trap(close,fvLo-0.3*atr,fvLo,fvHi,fvHi+0.3*atr); if(fv<0) mInFvBear=AF_MF_Trap(close,fvLo-0.3*atr,fvLo,fvHi,fvHi+0.3*atr); // bobot dinamis: volatilitas tinggi -> level S/R & premium/discount // kurang andal; OB/FVG (imbalance) lebih relevan double vol=atr/close; double volFactor=(vol>0.002)? 0.70 : 1.0; double wOb=0.30; double wFv=0.25; double wSr=0.25*volFactor; double wPd=0.20*volFactor; double wSum2=wOb+wFv+wSr+wPd; wOb/=wSum2; wFv/=wSum2; wSr/=wSum2; wPd/=wSum2; AFFuzzyEval fz; fz.Reset(); fz.Rule(true, mInObBull, wOb); fz.Rule(false, mInObBear, wOb); fz.Rule(true, mInFvBull, wFv); fz.Rule(false, mInFvBear, wFv); fz.Rule(true, mAtSup, wSr); fz.Rule(false, mAtRes, wSr); fz.Rule(true, mDisc, wPd); fz.Rule(false, mPrem, wPd); fz.Finalize(out,out.name,out.tfName); out.reason=""; if(ob>0 && mInObBull>0.5) out.reason+=" +di OB bullish"; if(ob<0 && mInObBear>0.5) out.reason+=" -di OB bearish"; if(fv>0 && mInFvBull>0.5) out.reason+=" +di FVG bullish"; if(fv<0 && mInFvBear>0.5) out.reason+=" -di FVG bearish"; if(mAtSup>0.5) out.reason+=" +dekat support"; if(mAtRes>0.5) out.reason+=" -dekat resistance"; if(mDisc>0.5) out.reason+=" +zona diskon"; if(mPrem>0.5) out.reason+=" -zona premium"; if(out.reason=="") out.reason="tidak di zona signifikan"; } //+------------------------------------------------------------------+ //| AGEN S3 - ENTRY (E): "Apakah ada konfirmasi masuk?" | //| Menilai: liquidity sweep, CHoCH/MSS, displacement, OB/FVG segar. | //| Aturan kunci: ZONA + KONFIRMASI = setup valid. | //+------------------------------------------------------------------+ class AFAgentEntry { public: void Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const; }; void AFAgentEntry::Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const { string tfName=(e1.IsReady(slot))? AF_E2_TfName(e1.TfOf(slot)) : "?"; AF_SignalInit(out,"Entry",tfName); if(!e1.IsReady(slot)) { out.reason="engine1 belum siap"; return; } if(e1.Count(slot)0)? 1.0 : 0.0; double mSweepBear=(sweep<0)? 1.0 : 0.0; double mChochBull=(choch>0)? 1.0 : 0.0; double mChochBear=(choch<0)? 1.0 : 0.0; double mDispBull =(disp>0)? 1.0 : 0.0; double mDispBear =(disp<0)? 1.0 : 0.0; double mZoneBull=0.0, mZoneBear=0.0; if(ob>0) mZoneBull=MathMax(mZoneBull,AF_MF_Trap(close,obLo-0.3*atr,obLo,obHi,obHi+0.3*atr)); if(ob<0) mZoneBear=MathMax(mZoneBear,AF_MF_Trap(close,obLo-0.3*atr,obLo,obHi,obHi+0.3*atr)); if(fv>0) mZoneBull=MathMax(mZoneBull,AF_MF_Trap(close,fvLo-0.3*atr,fvLo,fvHi,fvHi+0.3*atr)); if(fv<0) mZoneBear=MathMax(mZoneBear,AF_MF_Trap(close,fvLo-0.3*atr,fvLo,fvHi,fvHi+0.3*atr)); // bobot dinamis: displacement kuat -> bobot konfirmasi dinaikkan double wSweep=0.25; double wChoch=0.30; double wDisp =0.20; double wZone =0.15; double wSetup=0.30; if(MathAbs(disp)>0) wDisp*=1.3; double wSum3=wSweep+wChoch+wDisp+wZone+wSetup; wSweep/=wSum3; wChoch/=wSum3; wDisp/=wSum3; wZone/=wSum3; wSetup/=wSum3; AFFuzzyEval fz; fz.Reset(); fz.Rule(true, mSweepBull, wSweep); fz.Rule(false, mSweepBear, wSweep); fz.Rule(true, mChochBull, wChoch); fz.Rule(false, mChochBear, wChoch); fz.Rule(true, mDispBull, wDisp); fz.Rule(false, mDispBear, wDisp); fz.Rule(true, mZoneBull, wZone); fz.Rule(false, mZoneBear, wZone); // aturan setup: ZONA + KONFIRMASI = sinyal kuat double confBull=MathMax(mSweepBull,mChochBull); double confBear=MathMax(mSweepBear,mChochBear); fz.Rule(true, MathMin(mZoneBull,confBull), wSetup); fz.Rule(false, MathMin(mZoneBear,confBear), wSetup); fz.Finalize(out,out.name,out.tfName); out.reason=""; if(sweep>0) out.reason+=" +sweep bawah"; if(sweep<0) out.reason+=" -sweep atas"; if(choch>0) out.reason+=" +CHoCH naik"; if(choch<0) out.reason+=" -CHoCH turun"; if(disp>0) out.reason+=" +displacement naik"; if(disp<0) out.reason+=" -displacement turun"; if(mZoneBull>0.5) out.reason+=" +di zona (OB/FVG)"; if(mZoneBear>0.5) out.reason+=" -di zona (OB/FVG)"; if(out.reason=="") out.reason="belum ada konfirmasi"; } //+------------------------------------------------------------------+ //| AGEN S4 - PRICE ACTION (P): "Kapan buka posisi?" | //| Menilai: pola candlestick (engulfing, pin bar, inside bar, | //| momentum, posisi close di range). | //+------------------------------------------------------------------+ class AFAgentPriceAction { public: void Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const; }; void AFAgentPriceAction::Compute(const AFEngine1MTF &e1,int slot,AFSignalOut &out) const { string tfName=(e1.IsReady(slot))? AF_E2_TfName(e1.TfOf(slot)) : "?"; AF_SignalInit(out,"PriceAction",tfName); if(!e1.IsReady(slot)) { out.reason="engine1 belum siap"; return; } if(e1.Count(slot)0)? 0.4+0.6*strength : 0.0; double mBear=(pat<0)? 0.4+0.6*strength : 0.0; // bobot dinamis: ranging -> pola reversal lebih berarti; // trending -> pola continuation lebih berarti double netMove=MathAbs(e1.Close(slot,0)-e1.Close(slot,20)); double rangeSize=rmax-rmin; double mTrend=(rangeSize>0.0)? MathMin(1.0,netMove/(0.5*rangeSize)) : 0.3; double mRange=AF_Clamp01(1.0-mTrend); double wRev=0.45*(0.6+0.4*mRange); double wCont=0.35*(0.6+0.4*mTrend); double wPos=0.20; double wSum4=wRev+wCont+wPos; wRev/=wSum4; wCont/=wSum4; wPos/=wSum4; // continuation: 2 bar terakhir searah bool b0=e1.Close(slot,0)>e1.Open(slot,0); bool b1=e1.Close(slot,1)>e1.Open(slot,1); double mContBull=(b0&&b1)? 1.0 : 0.0; double mContBear=(!b0&&!b1)? 1.0 : 0.0; double mPosBull=AF_MF_Trap(pos,0.65,0.75,1.0,1.0); // close di atas range double mPosBear=AF_MF_Trap(pos,0.0,0.0,0.25,0.35); // close di bawah range AFFuzzyEval fz; fz.Reset(); fz.Rule(true, mBull, wRev); fz.Rule(false, mBear, wRev); fz.Rule(true, mContBull, wCont); fz.Rule(false, mContBear, wCont); fz.Rule(true, mPosBull, wPos); fz.Rule(false, mPosBear, wPos); fz.Finalize(out,out.name,out.tfName); out.reason=""; if(pat>0) { if(strength>=0.99) out.reason="+engulfing naik"; else if(strength>=0.84) out.reason="+pin bar naik"; else if(strength>=0.69) out.reason="+momentum 2 bar"; else if(strength>=0.59) out.reason="+inside bar (lanjut naik)"; else out.reason="+bias candle naik"; } else if(pat<0) { if(strength>=0.99) out.reason="-engulfing turun"; else if(strength>=0.84) out.reason="-pin bar turun"; else if(strength>=0.69) out.reason="-momentum 2 bar"; else if(strength>=0.59) out.reason="-inside bar (lanjut turun)"; else out.reason="-bias candle turun"; } else out.reason="pola netral"; if(mRange>0.6) out.reason+=" (ranging)"; else if(mTrend>0.6) out.reason+=" (trending)"; } #endif // AF_ENGINE2_AGENTS_MQH