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