Centaur_Quant_Architecture/MQL5/Include/Models/CFuzzyFusion.mqh

179 lines
6.9 KiB
MQL5

//+------------------------------------------------------------------+
//| 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
//+------------------------------------------------------------------+