323 lines
11 KiB
Plaintext
323 lines
11 KiB
Plaintext
#ifndef PATTERN_HUNTER_MQH
|
|
#define PATTERN_HUNTER_MQH
|
|
#include "AgentBase.mqh"
|
|
|
|
#define PH_BARS 50
|
|
#define PH_NUM_PATTERNS 21 // pattern codes -10 to +10
|
|
|
|
class PatternHunter : public IAgent {
|
|
private:
|
|
double histHurst[PH_BARS];
|
|
double histADX[PH_BARS];
|
|
double histMA[PH_BARS];
|
|
double histMom[PH_BARS];
|
|
double histConsensus[PH_BARS];
|
|
int barCount;
|
|
int idx;
|
|
|
|
// Distribuzioni empiriche di ogni agente
|
|
RunningStats distHurst, distADX, distMA, distMom;
|
|
// Distribuzioni empiriche delle differenze temporali
|
|
RunningStats diffMom3, diffMA3;
|
|
// Distribuzione empirica della somma MA+Mom (per normalizzazione combo)
|
|
RunningStats sumMAMom;
|
|
// Correlazione MA-Mom
|
|
RunningCorrelation corrMAMom;
|
|
// Pattern-specific win rate tracking
|
|
RunningStats patternReturns[PH_NUM_PATTERNS];
|
|
int patternCounts[PH_NUM_PATTERNS];
|
|
int lastPatternCode;
|
|
|
|
void Push(double &arr[], double val) {
|
|
arr[idx] = val;
|
|
}
|
|
|
|
double Get(double &arr[], int lookback=0) const {
|
|
int i = idx - lookback;
|
|
if(i < 0) i += PH_BARS;
|
|
if(i < 0 || i >= PH_BARS) return 0;
|
|
return arr[i];
|
|
}
|
|
|
|
double AgentThr(const RunningStats &ds) const {
|
|
// Serve almeno 1/3 della finestra per avere una stima affidabile
|
|
if(ds.Count() < PH_BARS / 3) return 1.0 / MathSqrt(MathMax(1, ds.Count()));
|
|
// Minimo: SE della media (non può essere zero con dati finiti)
|
|
return MathMax(1.0 / MathSqrt((double)MathMax(1, ds.Count())), ds.Std());
|
|
}
|
|
|
|
double SafeDenom(double v, double fallback) const {
|
|
double eps = DATA_EPS(fallback);
|
|
return (MathAbs(v) > eps) ? v : fallback;
|
|
}
|
|
|
|
int SignCount(const double &zH, const double &zA, const double &zM, const double &zMom) {
|
|
double tH = AgentThr(distHurst);
|
|
double tA = AgentThr(distADX);
|
|
double tM = AgentThr(distMA);
|
|
double tM2 = AgentThr(distMom);
|
|
int pos = 0, neg = 0;
|
|
if(zH > tH) pos++; else if(zH < -tH) neg++;
|
|
if(zA > tA) pos++; else if(zA < -tA) neg++;
|
|
if(zM > tM) pos++; else if(zM < -tM) neg++;
|
|
if(zMom > tM2) pos++; else if(zMom < -tM2) neg++;
|
|
return pos - neg;
|
|
}
|
|
|
|
public:
|
|
double patternZ;
|
|
string currentPattern;
|
|
double patternStrength;
|
|
|
|
PatternHunter(string n="Hunter", double w=1.0)
|
|
: IAgent(n, w), barCount(0), idx(0), patternZ(0),
|
|
currentPattern("none"), patternStrength(0),
|
|
distHurst(0.05, 30, 200), distADX(0.05, 30, 200),
|
|
distMA(0.05, 30, 200), distMom(0.05, 30, 200),
|
|
diffMom3(0.05, 20, 200), diffMA3(0.05, 20, 200),
|
|
sumMAMom(0.05, 20, 200), corrMAMom(0.05, 10),
|
|
lastPatternCode(0) {
|
|
for(int i=0; i<PH_BARS; i++) {
|
|
histHurst[i] = histADX[i] = histMA[i] = histMom[i] = histConsensus[i] = 0;
|
|
}
|
|
for(int i=0; i<PH_NUM_PATTERNS; i++) {
|
|
patternCounts[i] = 0;
|
|
patternReturns[i] = RunningStats(0.1, 3, 500);
|
|
}
|
|
}
|
|
|
|
double Analyze(const MarketData &data) override {
|
|
lastZScore = 0;
|
|
return 0;
|
|
}
|
|
|
|
void Interact(IAgent *&allAgents[], int count) override {
|
|
double hurst=0, adx=0, ma=0, mom=0, consensus=0;
|
|
int activeSignals = 0;
|
|
|
|
for(int i=0; i<count; i++) {
|
|
if(!allAgents[i].enabled) continue;
|
|
string n = allAgents[i].name;
|
|
if(n == "Hurst") { hurst = allAgents[i].lastZScore; activeSignals++; }
|
|
if(n == "ADX") { adx = allAgents[i].lastZScore; activeSignals++; }
|
|
if(n == "MA") { ma = allAgents[i].lastZScore; activeSignals++; }
|
|
if(n == "Momentum") { mom = allAgents[i].lastZScore; activeSignals++; }
|
|
if(n == "Consensus") consensus = allAgents[i].lastZScore;
|
|
}
|
|
|
|
// Store in history + update distribuzioni individuali
|
|
idx = (idx + 1) % PH_BARS;
|
|
Push(histHurst, hurst); distHurst.Update(hurst);
|
|
Push(histADX, adx); distADX.Update(adx);
|
|
Push(histMA, ma); distMA.Update(ma);
|
|
Push(histMom, mom); distMom.Update(mom);
|
|
Push(histConsensus, consensus);
|
|
if(barCount < PH_BARS) barCount++;
|
|
|
|
// Aggiorna distribuzioni delle differenze temporali (3-bar changes)
|
|
if(barCount > 2) {
|
|
double dMom = mom - Get(histMom, 2);
|
|
double dMa = ma - Get(histMA, 2);
|
|
diffMom3.Update(dMom);
|
|
diffMA3.Update(dMa);
|
|
}
|
|
sumMAMom.Update(ma + mom);
|
|
corrMAMom.Update(ma, mom);
|
|
|
|
// Soglie dinamiche per ogni agente
|
|
double tH = AgentThr(distHurst);
|
|
double tA = AgentThr(distADX);
|
|
double tM = AgentThr(distMA);
|
|
double tM2 = AgentThr(distMom);
|
|
|
|
// Soglia prodotto basata su Std empirici
|
|
double agreeThr = tM * tM2;
|
|
double divergeThr = -tM * tM2;
|
|
|
|
// Consensus per regime con Std empirico combinato
|
|
double regimeSum = hurst + adx;
|
|
double regimeThr = MathSqrt(tH * tH + tA * tA);
|
|
|
|
// Variazioni temporali: Std empirico delle differenze reali
|
|
double momDeltaThr = SafeDenom(diffMom3.Std(), tM2);
|
|
double maDeltaThr = SafeDenom(diffMA3.Std(), tM);
|
|
|
|
// Normalizzazione combo MA+Mom: Std empirico della somma
|
|
double comboNorm = SafeDenom(sumMAMom.Std(), MathSqrt(tM*tM + tM2*tM2));
|
|
|
|
// Pattern detection
|
|
int signScore = SignCount(hurst, adx, ma, mom);
|
|
int consensusThr = activeSignals - 1;
|
|
bool allBull = (signScore >= consensusThr);
|
|
bool allBear = (signScore <= -consensusThr);
|
|
bool maMomAgree = (ma * mom > agreeThr);
|
|
bool maMomDiverge = (ma * mom < divergeThr);
|
|
bool regimeTrend = regimeSum > regimeThr;
|
|
bool regimeRange = regimeSum < -regimeThr;
|
|
|
|
double momNow = mom;
|
|
double mom3ago = Get(histMom, 2);
|
|
double mom6ago = Get(histMom, 5);
|
|
bool momAccel = (momNow > mom3ago + momDeltaThr && mom3ago > mom6ago + momDeltaThr);
|
|
bool momDecel = (momNow < mom3ago - momDeltaThr && mom3ago < mom6ago - momDeltaThr);
|
|
|
|
double maNow = ma;
|
|
double ma3ago = Get(histMA, 2);
|
|
bool maRising = (maNow > ma3ago + maDeltaThr);
|
|
bool maFalling = (maNow < ma3ago - maDeltaThr);
|
|
|
|
int pCode = 0;
|
|
string pName = "none";
|
|
double pZ = 0;
|
|
|
|
// Conteggio agenti attivi per la media (solo quelli che contribuiscono)
|
|
double nAvg = (double)MathMax(1, activeSignals);
|
|
|
|
if(allBull && maMomAgree && regimeTrend) {
|
|
pCode = 10; pName = "perfect_bull";
|
|
pZ = (hurst + adx + ma + mom) / nAvg;
|
|
}
|
|
else if(allBear && maMomAgree && regimeTrend) {
|
|
pCode = -10; pName = "perfect_bear";
|
|
pZ = (hurst + adx + ma + mom) / nAvg;
|
|
}
|
|
else if(maMomAgree && regimeTrend && momAccel) {
|
|
pCode = 8; pName = "trend_accel";
|
|
pZ = (ma + mom) / comboNorm;
|
|
}
|
|
else if(maMomAgree && regimeTrend && momDecel) {
|
|
pCode = 6; pName = "trend_fatigue";
|
|
double fatigueFactor = 1.0 - MathAbs(SHARED_trendStrength);
|
|
pZ = (ma + mom) / comboNorm * fatigueFactor;
|
|
}
|
|
else if(regimeRange && maMomDiverge && MathAbs(mom) > tM2) {
|
|
pCode = 7; pName = "range_reversal";
|
|
pZ = -mom;
|
|
}
|
|
else if(regimeRange && maMomAgree && MathAbs(mom) < tM2) {
|
|
pCode = 3; pName = "range_quiet";
|
|
pZ = 0;
|
|
}
|
|
else if(maMomDiverge && MathAbs(mom) > tM2 && MathAbs(ma) < tM) {
|
|
pCode = 5; pName = "momentum_spike";
|
|
double trust = 1.0 - MathMin(1.0, MathAbs(ma) / SafeDenom(tM, 1.0/MathSqrt(MathMax(1, (double)PH_BARS))));
|
|
pZ = mom * trust;
|
|
}
|
|
else if(regimeTrend && maMomDiverge && MathAbs(ma) > tM) {
|
|
pCode = 4; pName = "pullback";
|
|
pZ = ma;
|
|
}
|
|
else if(signScore > 0) {
|
|
pCode = 2; pName = "leaning_bull";
|
|
double margin = (signScore - 1) / (nAvg - 1.0);
|
|
pZ = MathTanh(margin);
|
|
}
|
|
else if(signScore < 0) {
|
|
pCode = -2; pName = "leaning_bear";
|
|
double margin = (-signScore - 1) / (nAvg - 1.0);
|
|
pZ = -MathTanh(margin);
|
|
}
|
|
else if(barCount < PH_BARS) {
|
|
pCode = 0; pName = "warming";
|
|
pZ = 0;
|
|
}
|
|
|
|
currentPattern = pName;
|
|
patternZ = pZ;
|
|
patternStrength = 1.0 - MathExp(-MathAbs(pZ));
|
|
|
|
SHARED_patternCode = pCode;
|
|
SHARED_patternName = pName;
|
|
|
|
lastPatternCode = pCode;
|
|
lastZScore = CalibrateZ(pZ);
|
|
}
|
|
|
|
void Learn(double predictedZ, double actualReturnZ) override {
|
|
IAgent::Learn(predictedZ, actualReturnZ);
|
|
int codeIdx = lastPatternCode + 10;
|
|
if(codeIdx >= 0 && codeIdx < PH_NUM_PATTERNS) {
|
|
patternReturns[codeIdx].Update(actualReturnZ);
|
|
patternCounts[codeIdx]++;
|
|
}
|
|
}
|
|
|
|
void Save(int fh) const override {
|
|
IAgent::Save(fh);
|
|
for(int i=0; i<PH_BARS; i++) {
|
|
FileWriteDouble(fh, histHurst[i]);
|
|
FileWriteDouble(fh, histADX[i]);
|
|
FileWriteDouble(fh, histMA[i]);
|
|
FileWriteDouble(fh, histMom[i]);
|
|
FileWriteDouble(fh, histConsensus[i]);
|
|
}
|
|
FileWriteInteger(fh, barCount);
|
|
FileWriteInteger(fh, idx);
|
|
distHurst.Save(fh);
|
|
distADX.Save(fh);
|
|
distMA.Save(fh);
|
|
distMom.Save(fh);
|
|
diffMom3.Save(fh);
|
|
diffMA3.Save(fh);
|
|
sumMAMom.Save(fh);
|
|
for(int i=0; i<PH_NUM_PATTERNS; i++) {
|
|
FileWriteInteger(fh, patternCounts[i]);
|
|
patternReturns[i].Save(fh);
|
|
}
|
|
}
|
|
void Load(int fh) override {
|
|
IAgent::Load(fh);
|
|
for(int i=0; i<PH_BARS; i++) {
|
|
histHurst[i] = FileReadDouble(fh);
|
|
histADX[i] = FileReadDouble(fh);
|
|
histMA[i] = FileReadDouble(fh);
|
|
histMom[i] = FileReadDouble(fh);
|
|
histConsensus[i]= FileReadDouble(fh);
|
|
}
|
|
barCount = FileReadInteger(fh);
|
|
idx = FileReadInteger(fh);
|
|
distHurst.Load(fh);
|
|
distADX.Load(fh);
|
|
distMA.Load(fh);
|
|
distMom.Load(fh);
|
|
diffMom3.Load(fh);
|
|
diffMA3.Load(fh);
|
|
sumMAMom.Load(fh);
|
|
for(int i=0; i<PH_NUM_PATTERNS; i++) {
|
|
patternCounts[i] = FileReadInteger(fh);
|
|
patternReturns[i].Load(fh);
|
|
}
|
|
}
|
|
|
|
void Reset() override {
|
|
IAgent::Reset();
|
|
barCount = 0;
|
|
idx = 0;
|
|
for(int i=0; i<PH_BARS; i++) {
|
|
histHurst[i] = histADX[i] = histMA[i] = histMom[i] = histConsensus[i] = 0;
|
|
}
|
|
distHurst.Reset();
|
|
distADX.Reset();
|
|
distMA.Reset();
|
|
distMom.Reset();
|
|
diffMom3.Reset();
|
|
diffMA3.Reset();
|
|
sumMAMom.Reset();
|
|
corrMAMom.Reset();
|
|
for(int i=0; i<PH_NUM_PATTERNS; i++) {
|
|
patternCounts[i] = 0;
|
|
patternReturns[i].Reset();
|
|
}
|
|
currentPattern = "none";
|
|
patternStrength = 0;
|
|
patternZ = 0;
|
|
}
|
|
|
|
string SignalInfo() const override {
|
|
return name + " z=" + StringFormat("%+.3f", lastZScore)
|
|
+ " pattern=" + currentPattern
|
|
+ " str=" + StringFormat("%.2f", patternStrength);
|
|
}
|
|
};
|
|
#endif
|