2026-08-21 10:01:21 +07:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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<b<c)
|
| | | double AF_MF_Tri(double x,double a,double b,double c)
|
| | | {
|
| | | if(x<=a || x>=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<b) return lo;
|
| | | return hi;
|
| | | }
|
| | |
|
| | | // Fungsi keanggotaan trapesium (a<=b<=c<=d); degenerasi aman
|
| | | double AF_MF_Trap(double x,double a,double b,double c,double d)
|
| | | {
|
| | | if(x<=a || 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(x<b) return lo;
|
| | | return hi;
|
| | | }
|
| | |
|
| | | //------------------------------------------------------------------
|
| | | // Evaluator fuzzy (Mamdani ringan): akumulasi aturan berbobot
|
| | | //------------------------------------------------------------------
|
| | | struct AFFuzzyEval
|
| | | {
|
| | | double buyAcc; // akumulasi bobot sisi BUY
|
| | | double sellAcc; // akumulasi bobot sisi SELL
|
| | | double wTot; // total bobot aturan yang menyala
|
| | | void Reset() { buyAcc=0.0; sellAcc=0.0; wTot=0.0; }
|
| | | void Rule(bool buySide,double fire,double weight)
|
| | | {
|
| | | if(fire<=0.0 || weight<=0.0) return;
|
| | | wTot+=weight;
|
| | | if(buySide) buyAcc+=fire*weight; else sellAcc+=fire*weight;
|
| | | }
|
| | | void Finalize(AFSignalOut &out,const string name,const string tfName) const
|
| | | {
|
| | | out.name=name;
|
| | | out.tfName=tfName;
|
| | | double denom=(wTot>0.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;i<m;i++) s+=MathAbs(e1.Close(slot,i)-e1.Open(slot,i));
|
| | | return s/(double)m;
|
| | | }
|
| | |
|
| | | double AF_AvgRange(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;i<m;i++) s+=(e1.High(slot,i)-e1.Low(slot,i));
|
| | | return s/(double)m;
|
| | | }
|
| | |
|
| | | // range min/max dari `lookback` bar tertutup + posisi close [0,1]
|
| | | // (0 = terendah range, 1 = tertinggi range)
|
| | | void AF_RangeStat(const AFEngine1MTF &e1,int slot,int lookback,double &rmin,double &rmax,double &pos)
|
| | | {
|
| | | rmin=0.0; rmax=0.0; pos=0.5;
|
| | | if(!e1.IsReady(slot)) return;
|
| | | int n=MathMin(lookback,e1.Count(slot));
|
| | | if(n<2) return;
|
| | | rmin=DBL_MAX;
|
| | | rmax=-DBL_MAX;
|
| | | for(int i=0;i<n;i++)
|
| | | {
|
| | | double h=e1.High(slot,i), l=e1.Low(slot,i);
|
| | | if(h>rmax) rmax=h;
|
| | | if(l<rmin) rmin=l;
|
| | | }
|
| | | if(rmax>rmin) 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.nHigh<AF_E2_MAX_PIVOTS)
|
| | | {
|
| | | sd.highs[sd.nHigh].time =e1.Time(slot,i);
|
| | | sd.highs[sd.nHigh].price=h;
|
| | | sd.highs[sd.nHigh].idx =i;
|
| | | sd.nHigh++;
|
| | | }
|
| | | }
|
| | | double l=e1.Low(slot,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))
|
| | | {
|
| | | if(sd.nLow<AF_E2_MAX_PIVOTS)
|
| | | {
|
| | | sd.lows[sd.nLow].time =e1.Time(slot,i);
|
| | | sd.lows[sd.nLow].price=l;
|
| | | sd.lows[sd.nLow].idx =i;
|
| | | sd.nLow++;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // Tren dari urutan pivot: +1 bullish, -1 bearish, 0 mixed; clarity [0,1]
|
| | | void AF_TrendFromSwing(const AFSwingData &sd,int &trend,double &clarity)
|
| | | {
|
| | | trend=0; clarity=0.0;
|
| | | int up=0, dn=0, pairs=0;
|
| | | int nH=MathMin(4,sd.nHigh);
|
| | | int nL=MathMin(4,sd.nLow);
|
| | | for(int i=0;i+1<nH;i++)
|
| | | {
|
| | | if(sd.highs[i].price>sd.highs[i+1].price) up++; else dn++;
|
| | | pairs++;
|
| | | }
|
| | | for(int i=0;i+1<nL;i++)
|
| | | {
|
| | | if(sd.lows[i].price>sd.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 && close<sd.lows[0].price) return -1;
|
| | | return 0;
|
| | | }
|
| | |
|
| | | // BOS: +1 bullish (HH baru pada tren naik), -1 bearish, 0 none
|
| | | int AF_DetectBos(const AFEngine1MTF &e1,int slot,const AFSwingData &sd,int trend)
|
| | | {
|
| | | if(!e1.IsReady(slot)) return 0;
|
| | | double close=e1.Close(slot,0);
|
| | | if(trend>0 && sd.nHigh>=1 && close>sd.highs[0].price) return 1;
|
| | | if(trend<0 && sd.nLow>=1 && close<sd.lows[0].price) return -1;
|
| | | return 0;
|
| | | }
|
| | |
|
| | | // Sweep likuiditas: +1 sell-side tersapu lalu harga kembali (bullish),
|
| | | // -1 buy-side tersapu lalu harga kembali (bearish), 0 none
|
| | | int AF_DetectSweep(const AFEngine1MTF &e1,int slot,const AFSwingData &sd,int lookback)
|
| | | {
|
| | | 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;i<n;i++)
|
| | | {
|
| | | if(bestBull<0 && e1.Low(slot,i)<levelLow && e1.Close(slot,i)>levelLow) bestBull=i;
|
| | | if(bestBear<0 && e1.High(slot,i)>levelHigh && e1.Close(slot,i)<levelHigh) bestBear=i;
|
| | | }
|
| | | if(bestBull>=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;
|
| | | }
|
| | |
|
2026-08-22 23:31:22 +07:00 | | | //------------------------------------------------------------------
|
| | | // 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;i<n;i++)
|
| | | {
|
| | | if(bestBull<0 && e1.Low(slot,i)<levelLow && e1.Close(slot,i)>levelLow) bestBull=i;
|
| | | if(bestBear<0 && e1.High(slot,i)>levelHigh && e1.Close(slot,i)<levelHigh) bestBear=i;
|
| | | }
|
| | | if(bestBull>=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<sd.lows[0].price) { onsetBar=0; return -1; }
|
| | | return 0;
|
| | | }
|
| | |
|
2026-08-22 22:32:56 +07:00 | | | //------------------------------------------------------------------
|
| | | // ZONE STATE CONTRACT (frozen §K; P3-S.12 F2)
|
| | | // A zone is a persistent entity identified by its causal formation bar.
|
| | | // State is recomputed deterministically from CLOSED bars each call
|
| | | // (Engine-2 stateless per-bar, P3-S.7 S-ST) — one formation = one zone,
|
| | | // never re-emitted per bar (S-8).
|
| | | // UNMITIGATED : ACTIVE — price never entered the zone.
|
| | | // PARTIALLY_FILLED : ACTIVE — price entered but NO full fill
|
| | | // (S-9: partial fill != mitigation).
|
| | | // FULLY_MITIGATED : terminal — full fill; NOT consumable.
|
| | | // invalidated : terminal flag; zone-level = full-fill (frozen
|
| | | // §M "invalidation = mitigation is the terminal
|
| | | // usable-state"); setup-level (F3) may set it.
|
| | | // Consumer rule: available iff mit_state in {UNMITIGATED, PARTIALLY_FILLED}
|
| | | // and NOT invalidated. Consumers read the NEWEST ACTIVE zone (S-9/S-14).
|
| | | // No age expiry (W_zone_age = OPEN NUMERIC PARAMETER, default none).
|
| | | //------------------------------------------------------------------
|
| | | enum AFZoneMitState
|
| | | {
|
| | | AF_ZONE_UNMITIGATED = 0,
|
| | | AF_ZONE_PARTIALLY_FILLED = 1,
|
| | | AF_ZONE_FULLY_MITIGATED = 2
|
| | | };
|
| | |
|
| | | struct AFZoneState
|
| | | {
|
| | | int bar; // formation bar (causal identity; reversed index)
|
| | | int dir; // +1 bullish / -1 bearish / 0 none
|
| | | double top; // upper bound
|
| | | double bot; // lower bound
|
| | | AFZoneMitState mit_state; // UNMITIGATED | PARTIALLY_FILLED | FULLY_MITIGATED
|
| | | bool partial_filled; // informational: price entered the zone
|
| | | bool invalidated; // terminal flag (zone-level = full-fill)
|
| | | bool IsActive() const
|
| | | {
|
| | | return (mit_state==AF_ZONE_UNMITIGATED || mit_state==AF_ZONE_PARTIALLY_FILLED)
|
| | | && !invalidated;
|
| | | }
|
| | | };
|
| | |
|
| | | // FVG zone state (S-9): wick full-fill — bull Low(j)<=bot / bear High(j)>=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)<bot /
|
| | | // bear 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)? (cl<bot) : (cl>top);
|
| | | 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; }
|
| | | }
|
| | | }
|
| | |
|
2026-08-21 10:01:21 +07:00 | | | // Order Block terakhir: bar lawan warna sebelum move kuat.
|
| | | // return +1 bullish OB, -1 bearish OB, 0 none; zona via zoneHi/zoneLo.
|
2026-08-22 22:32:56 +07:00 | | | // P3-S.12 F2: hanya zona AKTIF (belum full-mitigasi close-through) yang
|
| | | // dikembalikan; zona yang sudah dimitigasi dilewati (S-9, BUG-P3S5-001).
|
2026-08-21 10:01:21 +07:00 | | | 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(cnt<avgN+2) return 0;
|
| | | double avg=AF_AvgBody(e1,slot,avgN);
|
| | | if(avg<=0.0) return 0;
|
| | | for(int i=1;i<cnt-1;i++)
|
| | | {
|
| | | double bodyPrev=MathAbs(e1.Close(slot,i-1)-e1.Open(slot,i-1));
|
| | | if(bodyPrev<1.5*avg) continue;
|
| | | bool upMove=e1.Close(slot,i-1)>e1.Open(slot,i-1);
|
2026-08-22 22:32:56 +07:00 | | | int dir=0;
|
2026-08-21 10:01:21 +07:00 | | | if(upMove)
|
| | | {
|
2026-08-22 22:32:56 +07:00 | | | if(e1.Close(slot,i)<e1.Open(slot,i)) dir=1;
|
2026-08-21 10:01:21 +07:00 | | | }
|
| | | else
|
| | | {
|
2026-08-22 22:32:56 +07:00 | | | if(e1.Close(slot,i)>e1.Open(slot,i)) dir=-1;
|
2026-08-21 10:01:21 +07:00 | | | }
|
2026-08-22 22:32:56 +07:00 | | | 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;
|
2026-08-21 10:01:21 +07:00 | | | }
|
| | | return 0;
|
| | | }
|
| | |
|
| | | // FVG (imbalance) terakhir. +1 bullish, -1 bearish, 0 none; zona via out.
|
2026-08-22 22:32:56 +07:00 | | | // 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).
|
2026-08-21 10:01:21 +07:00 | | | 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);
|
2026-08-22 22:32:56 +07:00 | | | for(int i=0;i<=maxIdx;i++)
|
2026-08-21 10:01:21 +07:00 | | | {
|
| | | 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
|
| | | {
|
2026-08-22 22:32:56 +07:00 | | | 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;
|
2026-08-21 10:01:21 +07:00 | | | return 1;
|
| | | }
|
| | | double h0=e1.High(slot,i);
|
| | | double l2=e1.Low(slot,i+2);
|
| | | if(h0<l2) // bearish FVG
|
| | | {
|
2026-08-22 22:32:56 +07:00 | | | double bot=h0;
|
| | | double top=l2;
|
| | | 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;
|
2026-08-21 10:01:21 +07:00 | | | return -1;
|
| | | }
|
| | | }
|
| | | return 0;
|
| | | }
|
| | |
|
| | | // Pola price action dari 3 bar terakhir.
|
| | | // return +1 bullish, -1 bearish, 0 none; code=arah pola, strength [0,1].
|
| | | int AF_PatternPA(const AFEngine1MTF &e1,int slot,int &code,double &strength)
|
| | | {
|
| | | code=0; strength=0.0;
|
| | | if(!e1.IsReady(slot)) return 0;
|
| | | int cnt=e1.Count(slot);
|
| | | if(cnt<4) return 0;
|
| | | double o0=e1.Open(slot,0), h0=e1.High(slot,0), l0=e1.Low(slot,0), c0=e1.Close(slot,0);
|
| | | double o1=e1.Open(slot,1), h1=e1.High(slot,1), l1=e1.Low(slot,1), c1=e1.Close(slot,1);
|
| | | bool b0=(c0>o0), 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)<AF_E2_MIN_BARS) { out.reason="data kurang"; return; }
|
| | |
|
| | | AFSwingData sd;
|
| | | AF_BuildSwing(e1,slot,sd,AF_E2_PIVOT_LOOKBACK);
|
| | | int trend=0;
|
| | | double clarity=0.0;
|
| | | AF_TrendFromSwing(sd,trend,clarity);
|
| | | int choch=AF_DetectChoch(e1,slot,sd,trend);
|
| | | int bos =AF_DetectBos(e1,slot,sd,trend);
|
| | | int sweep=AF_DetectSweep(e1,slot,sd,AF_E2_SWEEP_LOOKBACK);
|
| | | double rmin=0.0, rmax=0.0, pos=0.5;
|
| | | AF_RangeStat(e1,slot,AF_E2_LOOKBACK_PD,rmin,rmax,pos);
|
| | |
|
| | | // fuzzy variables
|
| | | double mBull=(trend>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)<AF_E2_MIN_BARS) { out.reason="data kurang"; return; }
|
| | |
|
| | | double close=e1.Close(slot,0);
|
| | | double atr=e1.ATR(slot,14);
|
| | | if(atr<=0.0) { out.reason="ATR 0"; return; }
|
| | |
|
| | | double obHi=0.0, obLo=0.0;
|
| | | int ob=AF_FindOrderBlock(e1,slot,AF_E2_LOOKBACK_AVG,obHi,obLo);
|
| | | double fvHi=0.0, fvLo=0.0;
|
| | | int fv=AF_FindFVG(e1,slot,AF_E2_FVG_LOOKBACK,fvHi,fvLo);
|
| | |
|
| | | AFSwingData sd;
|
| | | AF_BuildSwing(e1,slot,sd,AF_E2_PIVOT_LOOKBACK);
|
| | | double distSup=DBL_MAX, distRes=DBL_MAX;
|
| | | int nS=MathMin(12,sd.nLow);
|
| | | int nR=MathMin(12,sd.nHigh);
|
| | | for(int i=0;i<nS;i++)
|
| | | {
|
| | | double d=close-sd.lows[i].price;
|
| | | if(d>=0.0 && d<distSup) distSup=d;
|
| | | }
|
| | | for(int i=0;i<nR;i++)
|
| | | {
|
| | | double d=sd.highs[i].price-close;
|
| | | if(d>=0.0 && d<distRes) distRes=d;
|
| | | }
|
| | | double mAtSup=(distSup<DBL_MAX)? AF_Clamp01(1.0-distSup/atr) : 0.0; // 1 = tepat di support
|
| | | double mAtRes=(distRes<DBL_MAX)? AF_Clamp01(1.0-distRes/atr) : 0.0; // 1 = tepat di resistance
|
| | |
|
| | | double rmin=0.0, rmax=0.0, pos=0.5;
|
| | | AF_RangeStat(e1,slot,AF_E2_LOOKBACK_PD,rmin,rmax,pos);
|
| | | double mDisc=AF_MF_Trap(pos,0.0,0.0,0.30,0.45);
|
| | | double mPrem=AF_MF_Trap(pos,0.55,0.70,1.0,1.0);
|
| | |
|
| | | double mInObBull=0.0, mInObBear=0.0;
|
| | | if(ob>0) 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)<AF_E2_MIN_BARS) { out.reason="data kurang"; return; }
|
| | |
|
| | | AFSwingData sd;
|
| | | AF_BuildSwing(e1,slot,sd,AF_E2_PIVOT_LOOKBACK);
|
| | | int trend=0;
|
| | | double clarity=0.0;
|
| | | AF_TrendFromSwing(sd,trend,clarity);
|
| | | int sweep=AF_DetectSweep(e1,slot,sd,AF_E2_SWEEP_LOOKBACK);
|
| | | int choch=AF_DetectChoch(e1,slot,sd,trend);
|
| | | int disp =AF_DetectDisplacement(e1,slot,AF_E2_LOOKBACK_AVG);
|
| | | double obHi=0.0, obLo=0.0;
|
| | | int ob=AF_FindOrderBlock(e1,slot,AF_E2_LOOKBACK_AVG,obHi,obLo);
|
| | | double fvHi=0.0, fvLo=0.0;
|
| | | int fv=AF_FindFVG(e1,slot,20,fvHi,fvLo);
|
| | | double close=e1.Close(slot,0);
|
| | | double atr=e1.ATR(slot,14);
|
| | | if(atr<=0.0) { out.reason="ATR 0"; return; }
|
| | |
|
| | | double mSweepBull=(sweep>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)<AF_E2_MIN_BARS) { out.reason="data kurang"; return; }
|
| | |
|
| | | int code=0;
|
| | | double strength=0.0;
|
| | | int pat=AF_PatternPA(e1,slot,code,strength);
|
| | | double rmin=0.0, rmax=0.0, pos=0.5;
|
| | | AF_RangeStat(e1,slot,20,rmin,rmax,pos);
|
| | |
|
| | | double mBull=(pat>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
|