//+------------------------------------------------------------------+ //| CFuzzyFusion.mqh | //| Multi-Agent Fuzzy Fusion Engine (Centaur Quant) | //+------------------------------------------------------------------+ //| PURPOSE | //| Semua keputusan membuka posisi dihasilkan dari fusi fuzzy | //| kontribusi agen: struktur pasar (OB+FVG/SMC), trend (EMA), | //| momentum (RSI), regime volatilitas (ATR) dan AI advisory (LLM). | //| - Fuzzifikasi : tiap skor agen (0..100) -> derajat keanggotaan | //| lemah/sedang/kuat (fungsi keanggotaan trapesium/segitiga). | //| - Agregasi : bobot tertimbang DINAMIS per agen. | //| - Defuzzifikasi: centroid -> confidence fusi 0..100. | //| - Bobot adaptif: diperbarui online dari outcome trade (win/loss | //| via R-multiple) - agen yang sering benar naik bobotnya. | //| Anti-Veto: tidak ada agen tunggal (termasuk AI) yang memveto; | //| keputusan selalu kolektif (fuzzy). | //+------------------------------------------------------------------+ #ifndef CFUZZYFUSION_MQH #define CFUZZYFUSION_MQH enum ENUM_FUZZY_AGENT { FZ_STRUCTURE = 0, // struktur pasar (OB+FVG) - SMC FZ_TREND, // trend (EMA 50/200) FZ_MOMENTUM, // momentum (RSI 14) FZ_VOLATILITY, // regime volatilitas (ATR 14/50) FZ_AI, // AI advisory (LLM bridge) FZ_AGENTS_TOTAL }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CFuzzyFusion { private: double m_base[FZ_AGENTS_TOTAL]; // bobot dasar (dari input) double m_weight[FZ_AGENTS_TOTAL]; // bobot dinamis (ternormalisasi, sum=1) double m_perf[FZ_AGENTS_TOTAL]; // EWMA performa per agen [-1..1] double m_score[FZ_AGENTS_TOTAL]; // skor agen terakhir 0..100 double m_alpha; // laju adaptasi bobot (0..1) double m_min_w; // clamp bobot bawah double m_max_w; // clamp bobot atas double Clamp(const double v, const double lo, const double hi) { return (v < lo ? lo : (v > hi ? hi : v)); } //--- fungsi keanggotaan fuzzy (domain 0..100) --- double MuWeak(const double x) // trapesium [0,25,45,100) { return (x <= 25.0 ? 1.0 : (x >= 45.0 ? 0.0 : (45.0 - x) / 20.0)); } double MuModerate(const double x) // segitiga [25,50,75] { return MathMax(0.0, 1.0 - MathAbs(x - 50.0) / 25.0); } double MuStrong(const double x) // trapesium (0,55,75,100] { return (x <= 55.0 ? 0.0 : (x >= 75.0 ? 1.0 : (x - 55.0) / 20.0)); } void Normalize() { double sum = 0.0; for(int i = 0; i < FZ_AGENTS_TOTAL; i++) sum += m_weight[i]; if(sum <= 0.0) return; for(int i = 0; i < FZ_AGENTS_TOTAL; i++) m_weight[i] /= sum; } public: CFuzzyFusion() { for(int i = 0; i < FZ_AGENTS_TOTAL; i++) { m_base[i] = 1.0 / FZ_AGENTS_TOTAL; m_weight[i] = m_base[i]; m_perf[i] = 0.0; m_score[i] = 50.0; } m_alpha = 0.10; m_min_w = 0.05; m_max_w = 0.60; } void Configure(const double &baseWeights[], const double alpha, const double minW, const double maxW) { m_alpha = Clamp(alpha, 0.001, 0.5); m_min_w = Clamp(minW, 0.01, 0.4); m_max_w = Clamp(maxW, m_min_w, 0.9); for(int i = 0; i < FZ_AGENTS_TOTAL; i++) { m_base[i] = Clamp(baseWeights[i], 0.0, 1.0); m_weight[i] = m_base[i]; } Normalize(); } void SetScore(const ENUM_FUZZY_AGENT a, const double s) { if(a >= 0 && a < FZ_AGENTS_TOTAL) m_score[a] = Clamp(s, 0.0, 100.0); } double Score(const ENUM_FUZZY_AGENT a) const { return (a >= 0 && a < FZ_AGENTS_TOTAL) ? m_score[a] : 50.0; } double Weight(const ENUM_FUZZY_AGENT a) const { return (a >= 0 && a < FZ_AGENTS_TOTAL) ? m_weight[a] : 0.0; } //--- defuzzifikasi: centroid bobot-linguistik -> confidence 0..100 --- double Fuse() { double w = 0.0, m = 0.0, s = 0.0; for(int i = 0; i < FZ_AGENTS_TOTAL; i++) { const double x = m_score[i]; const double wt = m_weight[i]; w += wt * MuWeak(x); m += wt * MuModerate(x); s += wt * MuStrong(x); } const double denom = w + m + s; if(denom < 1e-9) return 50.0; return (w * 20.0 + m * 50.0 + s * 80.0) / denom; } //--- adaptasi bobot dinamis dari outcome trade --- void UpdateWeights(const bool won, const double &scores[]) { for(int i = 0; i < FZ_AGENTS_TOTAL; i++) { const double sc = Clamp(scores[i], 0.0, 100.0); const double signal = ((won && sc >= 55.0) || (!won && sc < 45.0)) ? 1.0 : -1.0; m_perf[i] = (1.0 - m_alpha) * m_perf[i] + m_alpha * signal; m_weight[i] = Clamp(m_base[i] * (0.5 + m_perf[i]), m_min_w, m_max_w); } Normalize(); } //--- persistensi bobot dinamis (file di MQL5\Files) --- bool Save(const string path) { int h = FileOpen(path, FILE_WRITE | FILE_TXT | FILE_ANSI); if(h == INVALID_HANDLE) return false; FileWriteString(h, StringFormat("CFZV1 alpha=%.4f min=%.2f max=%.2f\r\n", m_alpha, m_min_w, m_max_w)); for(int i = 0; i < FZ_AGENTS_TOTAL; i++) FileWriteString(h, StringFormat("%d %.4f %.4f\r\n", i, m_perf[i], m_weight[i])); FileClose(h); return true; } bool Load(const string path) { int h = FileOpen(path, FILE_READ | FILE_TXT | FILE_ANSI); if(h == INVALID_HANDLE) return false; FileReadString(h); // header (diabaikan) for(int i = 0; i < FZ_AGENTS_TOTAL && !FileIsEnding(h); i++) { const string l = FileReadString(h); string parts[]; const int n = StringSplit(l, ' ', parts); if(n >= 3) { const int idx = (int)StringToInteger(parts[0]); const double perf = StringToDouble(parts[1]); const double wgt = StringToDouble(parts[2]); if(idx >= 0 && idx < FZ_AGENTS_TOTAL) { m_perf[idx] = Clamp(perf, -1.0, 1.0); m_weight[idx] = Clamp(wgt, m_min_w, m_max_w); } } } FileClose(h); Normalize(); return true; } }; #endif //+------------------------------------------------------------------+