179 lines
6.9 KiB
MQL5
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
| |||
| |||
//+------------------------------------------------------------------+
|