MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes

This commit is contained in:
pietro_giacobazzi
2026-06-13 13:58:38 +02:00
commit 5c00f17121
19 changed files with 5522 additions and 0 deletions
@@ -0,0 +1,112 @@
#ifndef AGENT_BASE_MQH
#define AGENT_BASE_MQH
#include "../Core/MarketData.mqh"
#include "../Core/Statistics.mqh"
double SHARED_regimeZ = 0.0;
double SHARED_regimeH = 0.5;
double SHARED_trendStrength = 0.0;
double SHARED_adxZ = 0.0;
double SHARED_adxRaw = 0.0;
double SHARED_regimeConsensus = 0.0;
double SHARED_regimeAgreement = 0.0;
double SHARED_patternCode = 0.0;
string SHARED_patternName = "";
class IAgent {
public:
string name;
double weight;
bool enabled;
string symbol;
ENUM_TIMEFRAMES timeframe;
double lastZScore;
double lastRawSignal;
KalmanNormalizer signalStats;
// --- Learning infrastructure ---
RunningStats predictionError; // predictedZ - actualReturnZ (bias)
RunningCorrelation predCorr; // correlation predictedZ vs actualReturnZ
int learnMinSamples; // minimo campioni prima di applicare learning
IAgent(string n, double w=1.0)
: name(n), weight(w), enabled(true), symbol(""), timeframe(PERIOD_CURRENT),
lastZScore(0), lastRawSignal(0), signalStats(0.001, 1.0, 30),
predictionError(0.1, 5, 500), predCorr(0.1, 5), learnMinSamples(5) {}
virtual ~IAgent() {}
virtual void Init(string sym, ENUM_TIMEFRAMES tf) {
symbol = sym;
timeframe = tf;
}
virtual void Release() {}
virtual double Analyze(const MarketData &data) = 0;
virtual void Interact(IAgent *&allAgents[], int count) {}
// --- Apprendimento da trade outcome ---
// predictedZ = z-score dell'agente al momento dell'entrata
// actualReturnZ = ritorno normalizzato del trade chiuso
virtual void Learn(double predictedZ, double actualReturnZ) {
predictionError.Update(predictedZ - actualReturnZ);
predCorr.Update(predictedZ, actualReturnZ);
}
// Applica bias correction + confidence scaling a un raw z-score
double CalibrateZ(double rawZ) {
if(predictionError.Count() < learnMinSamples) return rawZ;
double corrected = rawZ;
// Bias correction: solo se statisticamente significativo (> 2 SE)
double bias = predictionError.Mean();
double biasSE = predictionError.Std() / MathSqrt((double)predictionError.Count());
if(MathAbs(bias) > 2.0 * biasSE) {
corrected -= bias * 0.3;
}
// Confidence scaling: se correlazione bassa o negativa, riduci magnitudine
if(predCorr.Ready()) {
double rho = predCorr.Correlation();
if(rho < 0.2) {
corrected *= MathMax(0.05, MathMax(0.0, rho) / 0.2);
}
}
return corrected;
}
virtual void Save(int fh) const {
FileWriteInteger(fh, 3);
signalStats.Save(fh);
predictionError.Save(fh);
predCorr.Save(fh);
}
virtual void Load(int fh) {
int ver = FileReadInteger(fh);
if(ver == 3) {
signalStats.Load(fh);
predictionError.Load(fh);
predCorr.Load(fh);
}
else if(ver == 2) {
signalStats.Load(fh);
}
}
virtual void Reset() {
signalStats.Reset();
predictionError.Reset();
lastZScore = 0;
lastRawSignal = 0;
}
virtual string SignalInfo() const {
return name + " z=" + StringFormat("%+.3f", lastZScore);
}
};
#endif
@@ -0,0 +1,182 @@
#ifndef MA_AGENT_MQH
#define MA_AGENT_MQH
#include "AgentBase.mqh"
#include "../Core/PeriodCalculator.mqh"
class MAAgent : public IAgent {
private:
RunningStats slopeStats;
int period;
int minPeriod, maxPeriod;
int maHandle;
int atrHandle;
int lastMAPeriod;
int barCount;
// Sub-signal learning: correlazione di ogni sub-signal col ritorno
RunningCorrelation priceToMaCorr;
RunningCorrelation slopeCorr;
double lastZ1; // priceToMa z-score
double lastZ2; // slope z-score
// Cache per Interact (ri-calcolo con regime fresco)
double m_currentClose;
int m_basePeriod;
void RecreateMA(int p) {
if(maHandle != INVALID_HANDLE) IndicatorRelease(maHandle);
maHandle = iMA(symbol, timeframe, p, 0, MODE_SMA, PRICE_CLOSE);
lastMAPeriod = p;
}
void RecreateATR(int p) {
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
atrHandle = iATR(symbol, timeframe, p);
}
double GetMA(int shift=0) {
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(maHandle, 0, shift, 1, buf) < 1) return 0;
return buf[0];
}
double GetATR() {
if(atrHandle == INVALID_HANDLE) RecreateATR(14);
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(atrHandle, 0, 0, 1, buf) < 1) return 0;
return buf[0];
}
public:
MAAgent(string n="MA", double w=1.0, int minP=8, int maxP=40)
: IAgent(n, w), slopeStats(0.05, 30, 500),
period(14), minPeriod(minP), maxPeriod(maxP),
maHandle(INVALID_HANDLE), atrHandle(INVALID_HANDLE), lastMAPeriod(0), barCount(0),
priceToMaCorr(0.1, 5), slopeCorr(0.1, 5), lastZ1(0), lastZ2(0) { signalStats.SetR(5.0); }
void Init(string sym, ENUM_TIMEFRAMES tf) override {
IAgent::Init(sym, tf);
maHandle = INVALID_HANDLE;
atrHandle = INVALID_HANDLE;
lastMAPeriod = 0;
}
void Release() override {
if(maHandle != INVALID_HANDLE) IndicatorRelease(maHandle);
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
maHandle = INVALID_HANDLE;
atrHandle = INVALID_HANDLE;
}
double Analyze(const MarketData &data) override {
barCount++;
m_basePeriod = PeriodCalculator::AutoPeriod(data, minPeriod, maxPeriod);
m_currentClose = data.Close(0);
RecomputeWithRegime(SHARED_regimeConsensus, SHARED_regimeAgreement);
return lastZScore;
}
// Ri-calcola tutto con valori di regime freschi (chiamato da Analyze e Interact)
void RecomputeWithRegime(double regime, double agreement) {
double trendStr = MathAbs(regime);
double maxIncrease = (double)maxPeriod / MathMax(minPeriod, m_basePeriod) - 1.0;
double periodMult = 1.0 + trendStr * agreement * maxIncrease;
int newPeriod = (int)MathRound(m_basePeriod * periodMult);
if(newPeriod < minPeriod) newPeriod = minPeriod;
if(newPeriod > maxPeriod) newPeriod = maxPeriod;
if(newPeriod != period) {
period = newPeriod;
if(maHandle != INVALID_HANDLE && period != lastMAPeriod)
RecreateMA(period);
} else if(maHandle == INVALID_HANDLE || period != lastMAPeriod) {
RecreateMA(period);
}
double ma = GetMA(0);
double maPv = GetMA(1);
double atr = GetATR();
double epsAtrM = DATA_EPS(atr);
double epsMaM = DATA_EPS(ma);
if(MathAbs(atr) < epsAtrM || MathAbs(ma) < epsMaM) { lastZScore = 0; return; }
double priceToMa = (m_currentClose - ma) / atr;
double slope = (ma - maPv) / atr;
signalStats.Update(priceToMa);
slopeStats.Update(slope);
double z1 = signalStats.ZScore(priceToMa);
double z2 = slopeStats.ZScore(slope);
lastZ1 = z1;
lastZ2 = z2;
double wPrice, wSlope;
if(priceToMaCorr.Ready() && slopeCorr.Ready()) {
double r1 = MathMax(0.0, priceToMaCorr.Correlation());
double r2 = MathMax(0.0, slopeCorr.Correlation());
double sumR = r1 + r2 + DATA_EPS(r1 + r2);
wPrice = r1 / sumR;
wSlope = 1.0 - wPrice;
} else {
double s1 = signalStats.Std();
double s2 = slopeStats.Std();
double sumV = s1 + s2;
if(sumV < DATA_EPS(MathMax(s1, s2))) {
wPrice = wSlope = 1.0 / 2.0;
} else {
wPrice = s1 / sumV;
wSlope = 1.0 - wPrice;
}
}
double norm = MathSqrt(wPrice*wPrice + wSlope*wSlope);
lastZScore = (wPrice * z1 + wSlope * z2) / norm;
lastZScore = CalibrateZ(lastZScore);
lastRawSignal = priceToMa;
}
void Interact(IAgent *&allAgents[], int count) override {
// Rilegge regime fresco (dopo Interact di Consensus) e ri-calcola
RecomputeWithRegime(SHARED_regimeConsensus, SHARED_regimeAgreement);
}
void Learn(double predictedZ, double actualReturnZ) override {
IAgent::Learn(predictedZ, actualReturnZ);
// Sub-signal learning: quale componente ha predetto meglio?
priceToMaCorr.Update(lastZ1, actualReturnZ);
slopeCorr.Update(lastZ2, actualReturnZ);
}
void Save(int fh) const override {
IAgent::Save(fh);
slopeStats.Save(fh);
priceToMaCorr.Save(fh);
slopeCorr.Save(fh);
}
void Load(int fh) override {
IAgent::Load(fh);
slopeStats.Load(fh);
priceToMaCorr.Load(fh);
slopeCorr.Load(fh);
}
void Reset() override {
IAgent::Reset();
slopeStats.Reset();
priceToMaCorr.Reset();
slopeCorr.Reset();
lastZ1 = 0; lastZ2 = 0;
}
string SignalInfo() const override {
return name + " z=" + StringFormat("%+.3f", lastZScore)
+ " period=" + (string)period
+ " " + signalStats.ToString();
}
};
#endif
@@ -0,0 +1,147 @@
#ifndef MOMENTUM_AGENT_MQH
#define MOMENTUM_AGENT_MQH
#include "AgentBase.mqh"
#include "../Core/PeriodCalculator.mqh"
class MomentumAgent : public IAgent {
private:
RunningStats accelStats;
int period;
int minPeriod, maxPeriod;
int momHandle;
int lastMomPeriod;
double lastZ1; // mom z-score
double lastZ2; // accel z-score
RunningCorrelation momCorr;
RunningCorrelation accelCorr;
// Cache per Interact (ri-calcolo con regime fresco)
int m_basePeriod;
void Recreate(int p) {
if(momHandle != INVALID_HANDLE) IndicatorRelease(momHandle);
momHandle = iMomentum(symbol, timeframe, p, PRICE_CLOSE);
lastMomPeriod = p;
}
double GetMom(int shift=0) {
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(momHandle, 0, shift, 1, buf) < 1) return 0;
return buf[0];
}
public:
MomentumAgent(string n="Momentum", double w=1.0, int minP=6, int maxP=40)
: IAgent(n, w), accelStats(0.05, 30, 500),
period(14), minPeriod(minP), maxPeriod(maxP),
momHandle(INVALID_HANDLE), lastMomPeriod(0),
lastZ1(0), lastZ2(0), momCorr(0.1, 5), accelCorr(0.1, 5) { signalStats.SetR(2.0); }
void Init(string sym, ENUM_TIMEFRAMES tf) override {
IAgent::Init(sym, tf);
momHandle = INVALID_HANDLE;
lastMomPeriod = 0;
}
void Release() override {
if(momHandle != INVALID_HANDLE) IndicatorRelease(momHandle);
momHandle = INVALID_HANDLE;
}
double Analyze(const MarketData &data) override {
m_basePeriod = PeriodCalculator::AutoPeriod(data, minPeriod, maxPeriod);
RecomputeWithRegime(SHARED_regimeConsensus, SHARED_regimeAgreement);
return lastZScore;
}
void RecomputeWithRegime(double regime, double agreement) {
double trendStr = MathAbs(regime);
double maxDecrease = 1.0 - (double)minPeriod / MathMax(minPeriod, m_basePeriod);
double periodMult = 1.0 - trendStr * agreement * maxDecrease;
int newPeriod = (int)MathRound(m_basePeriod * periodMult);
if(newPeriod < minPeriod) newPeriod = minPeriod;
if(newPeriod > maxPeriod) newPeriod = maxPeriod;
if(newPeriod != period) {
period = newPeriod;
if(momHandle != INVALID_HANDLE && period != lastMomPeriod)
Recreate(period);
} else if(momHandle == INVALID_HANDLE || period != lastMomPeriod) {
Recreate(period);
}
double mom = GetMom(0) - 100.0;
double momPv = GetMom(1) - 100.0;
signalStats.Update(mom);
double accel = mom - momPv;
accelStats.Update(accel);
double z1 = signalStats.ZScore(mom);
double z2 = accelStats.ZScore(accel);
lastZ1 = z1;
lastZ2 = z2;
double wLevel, wAccel;
if(momCorr.Ready() && accelCorr.Ready()) {
double r1 = MathMax(0.0, momCorr.Correlation());
double r2 = MathMax(0.0, accelCorr.Correlation());
double sumR = r1 + r2 + DATA_EPS(r1 + r2);
wLevel = r1 / sumR;
wAccel = 1.0 - wLevel;
} else {
double s1 = signalStats.Std();
double s2 = accelStats.Std();
double epsSum = DATA_EPS(MathMax(s1, s2));
wLevel = (s1 + s2 > epsSum) ? s1 / (s1 + s2) : 1.0 / 2.0;
wAccel = 1.0 - wLevel;
}
double norm = MathSqrt(wLevel*wLevel + wAccel*wAccel);
lastZScore = (wLevel * z1 + wAccel * z2) / norm;
lastZScore = CalibrateZ(lastZScore);
lastRawSignal = mom;
}
void Interact(IAgent *&allAgents[], int count) override {
// Rilegge regime fresco (dopo Interact di Consensus) e ri-calcola
RecomputeWithRegime(SHARED_regimeConsensus, SHARED_regimeAgreement);
}
void Learn(double predictedZ, double actualReturnZ) override {
IAgent::Learn(predictedZ, actualReturnZ);
momCorr.Update(lastZ1, actualReturnZ);
accelCorr.Update(lastZ2, actualReturnZ);
}
void Save(int fh) const override {
IAgent::Save(fh);
accelStats.Save(fh);
momCorr.Save(fh);
accelCorr.Save(fh);
}
void Load(int fh) override {
IAgent::Load(fh);
accelStats.Load(fh);
momCorr.Load(fh);
accelCorr.Load(fh);
}
void Reset() override {
IAgent::Reset();
accelStats.Reset();
momCorr.Reset();
accelCorr.Reset();
lastZ1 = 0; lastZ2 = 0;
}
string SignalInfo() const override {
return name + " z=" + StringFormat("%+.3f", lastZScore)
+ " period=" + (string)period
+ " " + signalStats.ToString();
}
};
#endif
@@ -0,0 +1,322 @@
#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
@@ -0,0 +1,122 @@
#ifndef REGIME_ADX_MQH
#define REGIME_ADX_MQH
#include "AgentBase.mqh"
#include "../Core/PeriodCalculator.mqh"
class RegimeADX : public IAgent {
private:
int adxPeriod;
int userPeriod;
int adxHandle;
int lastADXPeriod;
int adxMinP, adxMaxP;
double prevZ;
void Recreate(int p) {
if(adxHandle != INVALID_HANDLE) IndicatorRelease(adxHandle);
adxHandle = iADX(symbol, timeframe, p);
lastADXPeriod = p;
}
double GetADX() {
if(adxHandle == INVALID_HANDLE) Recreate(adxPeriod);
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(adxHandle, 0, 0, 1, buf) < 1) return 0;
return buf[0];
}
double GetDI(int plusMinus=1, int shift=0) {
if(adxHandle == INVALID_HANDLE) Recreate(adxPeriod);
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(adxHandle, plusMinus, shift, 1, buf) < 1) return 0;
return buf[0];
}
public:
RegimeADX(string n="ADX", double w=1.0, int period=0)
: IAgent(n, w), userPeriod(period), adxPeriod(0), adxHandle(INVALID_HANDLE),
lastADXPeriod(0), adxMinP(7), adxMaxP(30), prevZ(0) { signalStats.SetR(50.0); }
void Init(string sym, ENUM_TIMEFRAMES tf) override {
IAgent::Init(sym, tf);
adxHandle = INVALID_HANDLE;
lastADXPeriod = 0;
}
void Release() override {
if(adxHandle != INVALID_HANDLE) IndicatorRelease(adxHandle);
adxHandle = INVALID_HANDLE;
}
double Analyze(const MarketData &data) override {
// Periodo: fisso se utente lo specifica, altrimenti data-driven + EWMA
if(userPeriod > 0) {
adxPeriod = userPeriod;
} else {
int newP = PeriodCalculator::AutoPeriod(data, adxMinP, adxMaxP);
if(adxPeriod <= 0) adxPeriod = newP;
else {
double pAlpha = 1.0 / (1.0 + signalStats.Count() * 0.05);
pAlpha = MathMax(0.05, pAlpha); // solo floor
adxPeriod = (int)MathRound(pAlpha * newP + (1.0 - pAlpha) * adxPeriod);
}
if(adxPeriod < adxMinP) adxPeriod = adxMinP;
if(adxPeriod > adxMaxP) adxPeriod = adxMaxP;
}
if(adxPeriod <= 0) { lastZScore = 0; return 0; }
if(adxHandle == INVALID_HANDLE || adxPeriod != lastADXPeriod)
Recreate(adxPeriod);
double adx = GetADX();
double epsAdx = DATA_EPS(adx);
if(MathAbs(adx) < epsAdx) { lastZScore = 0; return 0; }
// Normalizza ADX via EWMA
signalStats.Update(adx);
double zRaw = signalStats.ZScore(adx);
// EWMA alpha: scala con conteggio campioni, solo floor data-driven
double alpha = 1.0 / (1.0 + signalStats.Count() * 0.1);
double minAlpha = 1.0 / MathMax(2.0, (double)MathMax(1, adxPeriod));
alpha = MathMax(minAlpha, alpha); // solo floor, niente max clamp
prevZ = (1.0 - alpha) * prevZ + alpha * zRaw;
double calibrated = CalibrateZ(prevZ);
lastZScore = MathTanh(calibrated);
lastRawSignal = adx;
// Pubblica nel contesto condiviso
SHARED_adxZ = lastZScore;
SHARED_adxRaw = adx;
return lastZScore;
}
void Interact(IAgent *&allAgents[], int count) override {}
void Learn(double predictedZ, double actualReturnZ) override {}
void Save(int fh) const override {
IAgent::Save(fh);
FileWriteDouble(fh, prevZ);
}
void Load(int fh) override {
IAgent::Load(fh);
prevZ = FileReadDouble(fh);
}
void Reset() override {
IAgent::Reset();
prevZ = 0;
}
string SignalInfo() const override {
return name + " z=" + StringFormat("%+.3f", lastZScore)
+ " ADX=" + StringFormat("%.1f", SHARED_adxRaw)
+ " p=" + (string)adxPeriod
+ " " + signalStats.ToString();
}
};
#endif
@@ -0,0 +1,145 @@
#ifndef REGIME_CONSENSUS_MQH
#define REGIME_CONSENSUS_MQH
#include "AgentBase.mqh"
class RegimeConsensus : public IAgent {
private:
RunningStats adxStats, hurstStats;
RunningStats diffRegimeStats;
RunningCorrelation hurstCorr; // correlazione hurstZ con ritorno
RunningCorrelation adxCorr; // correlazione adxZ con ritorno
double lastHurstZ, lastADXZ; // raw z-scores per Learn()
double SafeDenom(double v, double fallback) const {
double eps = DATA_EPS(fallback);
return (MathAbs(v) > eps) ? v : fallback;
}
double ZScoreFallback() const { return 1.0; } // z-score ha per definizione σ=1
public:
RegimeConsensus(string n="Consensus", double w=1.0)
: IAgent(n, w), adxStats(0.05, 30, 200), hurstStats(0.05, 30, 200),
diffRegimeStats(0.05, 20, 200), hurstCorr(0.1, 5), adxCorr(0.1, 5),
lastHurstZ(0), lastADXZ(0) {}
double Analyze(const MarketData &data) override {
lastZScore = 0;
return 0;
}
void Interact(IAgent *&allAgents[], int count) override {
double hurstZ = 0, adxZ = 0;
double rawHurstZ = 0, rawADXZ = 0;
for(int i=0; i<count; i++) {
if(!allAgents[i].enabled) continue;
if(allAgents[i].name == "Hurst") { rawHurstZ = allAgents[i].lastRawSignal; hurstZ = allAgents[i].lastZScore; }
if(allAgents[i].name == "ADX") { rawADXZ = allAgents[i].lastRawSignal; adxZ = allAgents[i].lastZScore; }
}
lastHurstZ = hurstZ;
lastADXZ = adxZ;
hurstStats.Update(hurstZ);
adxStats.Update(adxZ);
double hStd = SafeDenom(hurstStats.Std(), ZScoreFallback());
double aStd = SafeDenom(adxStats.Std(), ZScoreFallback());
double combinedScale = MathSqrt(hStd * hStd + aStd * aStd);
double rawDiff = MathAbs(hurstZ - adxZ);
diffRegimeStats.Update(rawDiff);
// Agreement data-driven: quanto sono vicine relative alla loro volatilità tipica
double typicalDiff = SafeDenom(diffRegimeStats.Std(), combinedScale);
SHARED_regimeAgreement = 1.0 - MathMin(rawDiff / typicalDiff, 1.0);
// Consenso: media divisa per numero di fonti attive
double nActive = 0;
if(hurstStats.Count() > 0) nActive += 1.0;
if(adxStats.Count() > 0) nActive += 1.0;
nActive = MathMax(1.0, nActive);
SHARED_regimeConsensus = MathTanh((hurstZ + adxZ) / nActive * SHARED_regimeAgreement);
// Rilevamento pattern con soglie data-driven
long pattern = 0;
string pName = "none";
double hThr = SafeDenom(hStd, ZScoreFallback());
double aThr = SafeDenom(aStd, ZScoreFallback());
// Fattore divergenza basato sull'agreement storico
double agreeFactor = 1.0 + 1.0 / MathMax(1e-15, SHARED_regimeAgreement);
double divThreshold = combinedScale * agreeFactor;
// Soglia breakout: SE della differenza normalizzato per agreement
// Per due fonti indipendenti, SE_diff = √(hStd² + aStd²) / √nActive
// Agreement scala: più accordo → soglia più alta (breakout più significativo)
double nActiveRegime = 2.0;
double seDiff = combinedScale / MathSqrt(nActiveRegime);
double breakLower = seDiff * (1.0 + SHARED_regimeAgreement);
double breakUpper = divThreshold;
if(hurstZ > hThr && adxZ > aThr) {
pattern = 1; pName = "strong_trend";
}
else if(hurstZ < -hThr && adxZ < -aThr) {
pattern = 2; pName = "strong_range";
}
else if(rawDiff > divThreshold && (hurstZ > 0 || adxZ > 0)) {
pattern = 3; pName = "divergence";
}
else if(rawDiff > breakLower && rawDiff < breakUpper &&
MathAbs(hurstZ + adxZ) > combinedScale) {
pattern = 4; pName = "breakout_forming";
}
else if(MathAbs(SHARED_regimeConsensus) > combinedScale / MathSqrt(nActiveRegime)) {
pattern = 5; pName = "weak_bias";
}
SHARED_regimeConsensus = CalibrateZ(SHARED_regimeConsensus);
SHARED_regimeZ = SHARED_regimeConsensus;
SHARED_trendStrength = MathAbs(SHARED_regimeConsensus);
}
void Learn(double predictedZ, double actualReturnZ) override {
IAgent::Learn(predictedZ, actualReturnZ);
hurstCorr.Update(lastHurstZ, actualReturnZ);
adxCorr.Update(lastADXZ, actualReturnZ);
}
void Save(int fh) const override {
IAgent::Save(fh);
adxStats.Save(fh);
hurstStats.Save(fh);
diffRegimeStats.Save(fh);
hurstCorr.Save(fh);
adxCorr.Save(fh);
}
void Load(int fh) override {
IAgent::Load(fh);
adxStats.Load(fh);
hurstStats.Load(fh);
diffRegimeStats.Load(fh);
hurstCorr.Load(fh);
adxCorr.Load(fh);
}
void Reset() override {
IAgent::Reset();
adxStats.Reset();
hurstStats.Reset();
diffRegimeStats.Reset();
hurstCorr.Reset();
adxCorr.Reset();
lastHurstZ = 0; lastADXZ = 0;
}
string SignalInfo() const override {
return name + " z=" + StringFormat("%+.3f", SHARED_regimeConsensus)
+ " agree=" + StringFormat("%.2f", SHARED_regimeAgreement);
}
};
#endif
@@ -0,0 +1,284 @@
#ifndef REGIME_DETECTOR_MQH
#define REGIME_DETECTOR_MQH
#include "AgentBase.mqh"
#include "../Core/PeriodCalculator.mqh"
class RegimeDetector : public IAgent {
private:
int hurstPeriod;
int userPeriod;
int minPeriod, maxPeriod;
double prevZ;
int warmup;
int targetWindows;
int LogReturns(const double &close[], int len, double &ret[]) const {
int n = len - 1;
ArrayResize(ret, n);
for(int i=0; i<n; i++) {
double r = close[i] / close[i+1];
if(r <= 0) { ret[i] = 0; continue; }
ret[i] = MathLog(r);
}
return n;
}
double ComputeDFA(const double &close[], int len) {
targetWindows = MathMax(4, MathMin(14, len / 50));
int minN = MathMax(3, targetWindows);
if(len < targetWindows * minN * 2) return 0.5;
int maxN = MathMax(minN * 2, len / (targetWindows / 2));
if(maxN < minN * 2) return 0.5;
double returns[];
int nRet = LogReturns(close, len, returns);
if(nRet < maxN) return 0.5;
// Integra: profilo (somma cumulativa dei rendimenti)
double profile[];
ArrayResize(profile, nRet);
profile[0] = returns[0];
for(int i=1; i<nRet; i++)
profile[i] = profile[i-1] + returns[i];
// Varianza dei rendimenti per soglia data-scaled DFA
double retVar = 0;
for(int i=0; i<nRet; i++) retVar += returns[i] * returns[i];
retVar = MathMax(1e-15, retVar / nRet);
// Step derivato dal numero di window target
double step = MathPow((double)maxN / minN, 1.0 / (targetWindows - 1));
step = MathMax(1.3, MathMin(2.0, step));
double logF[], logN[];
ArrayResize(logF, targetWindows);
ArrayResize(logN, targetWindows);
int pts = 0;
for(int n = minN; n <= maxN; n = (int)(n * step) + 1) {
int m = nRet / n;
if(m < 3) continue;
if(pts >= targetWindows) break;
double sumF2 = 0;
int validWin = 0;
for(int j=0; j<m; j++) {
int base = j * n;
// OLS detrend lineare della finestra
double sx=0, sy=0, sxx=0, sxy=0;
for(int k=0; k<n; k++) {
double x = k;
double y = profile[base + k];
sx += x; sy += y;
sxx += x*x; sxy += x*y;
}
double slope = (n * sxy - sx * sy) / (n * sxx - sx * sx + 1e-15);
double intercept = (sy - slope * sx) / n;
// Varianza del residuo (dopo detrend)
double var = 0;
for(int k=0; k<n; k++) {
double fit = intercept + slope * k;
double res = profile[base + k] - fit;
var += res * res;
}
var /= n;
// Soglia: varianza attesa per unbiased RW = retVar * n
// DATA_EPS: soglia numerica scalata con la varianza attesa
double epsVar = DATA_EPS(retVar * n);
if(var < epsVar) continue;
sumF2 += var;
validWin++;
}
if(validWin < 2) continue;
double F = MathSqrt(sumF2 / validWin);
logF[pts] = MathLog(F);
logN[pts] = MathLog(n);
pts++;
}
if(pts < 3) return 0.5;
double sumX=0, sumY=0, sumXY=0, sumX2=0;
for(int i=0; i<pts; i++) {
sumX += logN[i];
sumY += logF[i];
sumXY += logN[i] * logF[i];
sumX2 += logN[i] * logN[i];
}
double H = (pts * sumXY - sumX * sumY) / (pts * sumX2 - sumX * sumX);
H = MathMax(0.01, MathMin(1.50, H));
return H;
}
// Fallback a R/S se DFA non converge
double ComputeHurst(const double &close[], int len) {
double H = ComputeDFA(close, len);
double hSe = MathSqrt(12.0 / len); // SE approssimato di Hurst per unbiased RW
if(H < hSe || H > 1.0 - hSe || MathAbs(H - 0.5) < hSe)
H = ComputeRS(close, len);
return MathMax(hSe, MathMin(1.0 - hSe, H));
}
double ComputeRS(const double &close[], int len) {
targetWindows = MathMax(3, MathMin(10, len / 60));
int minN = MathMax(3, targetWindows);
if(len < targetWindows * minN * 2) return 0.5;
int maxN = MathMax(minN * 2, len / (targetWindows / 2));
if(maxN < minN * 2) return 0.5;
double returns[];
int nRet = LogReturns(close, len, returns);
if(nRet < maxN) return 0.5;
// Varianza di riferimento per soglia data-scaled
double retVarRef = 0;
for(int i=0; i<nRet; i++) retVarRef += returns[i] * returns[i];
retVarRef = DATA_EPS(retVarRef / nRet);
double epsVarRS = DATA_EPS(retVarRef);
double step = MathPow((double)maxN / minN, 1.0 / (targetWindows - 1));
step = MathMax(1.3, MathMin(2.5, step));
double logRS[], logN[];
ArrayResize(logRS, targetWindows);
ArrayResize(logN, targetWindows);
int pts = 0;
for(int n = minN; n <= maxN; n = (int)(n * step) + 1) {
int m = nRet / n;
if(m < 2) continue;
if(pts >= targetWindows) break;
double sumRS = 0;
int validSub = 0;
for(int j=0; j<m; j++) {
int base = j * n;
double sum = 0, sumSq = 0;
for(int k=0; k<n; k++) {
double r = returns[base + k];
sum += r;
sumSq += r * r;
}
double mean = sum / n;
double var = sumSq / n - mean * mean;
double std = (var > epsVarRS) ? MathSqrt(var) : 0;
if(std < MathSqrt(epsVarRS)) continue;
double cumDev[];
ArrayResize(cumDev, n);
cumDev[0] = returns[base] - mean;
for(int k=1; k<n; k++)
cumDev[k] = cumDev[k-1] + returns[base + k] - mean;
int maxIdx = 0, minIdx = 0;
for(int k=1; k<n; k++) {
if(cumDev[k] > cumDev[maxIdx]) maxIdx = k;
if(cumDev[k] < cumDev[minIdx]) minIdx = k;
}
double R = cumDev[maxIdx] - cumDev[minIdx];
sumRS += R / std;
validSub++;
}
if(validSub < 1) continue;
double avgRS = sumRS / validSub;
logRS[pts] = MathLog(avgRS);
logN[pts] = MathLog(n);
pts++;
}
if(pts < 3) return 0.5;
double sumX=0, sumY=0, sumXY=0, sumX2=0;
for(int i=0; i<pts; i++) {
sumX += logN[i];
sumY += logRS[i];
sumXY += logN[i] * logRS[i];
sumX2 += logN[i] * logN[i];
}
double H = (pts * sumXY - sumX * sumY) / (pts * sumX2 - sumX * sumX);
return MathMax(0.01, MathMin(0.99, H));
}
public:
RegimeDetector(string n="Hurst", double w=1.0, int hp=0)
: IAgent(n, w), userPeriod(hp), hurstPeriod(0), minPeriod(40), maxPeriod(200), prevZ(0), warmup(0) { signalStats.SetR(0.05); }
double Analyze(const MarketData &data) override {
warmup++;
// Periodo: fisso se utente lo specifica, altrimenti data-driven + EWMA
if(userPeriod > 0) {
hurstPeriod = userPeriod;
} else {
int cycle = PeriodCalculator::DominantCycle(data.close, data.count, 20, 100);
// Periodo: max(2x ciclo, minWindows * targetWindows)
int minForWindows = targetWindows * MathMax(3, targetWindows);
int newP = MathMax(cycle * 2, minForWindows);
newP = MathMax(20, MathMin(200, newP));
if(hurstPeriod <= 0) hurstPeriod = newP;
else {
double alpha = 1.0 / (1.0 + warmup * 0.1);
double minAlpha = 1.0 / MathMax(2.0, (double)MathMax(1, hurstPeriod));
alpha = MathMax(minAlpha, alpha); // solo floor, niente max clamp
hurstPeriod = (int)MathRound(alpha * newP + (1.0 - alpha) * hurstPeriod);
}
if(hurstPeriod < minPeriod) hurstPeriod = minPeriod;
}
double H = ComputeHurst(data.close, MathMin(hurstPeriod, data.count));
signalStats.Update(H);
double zRaw = signalStats.ZScore(H);
// EWMA con alpha che scala con il numero di osservazioni
double alpha = 1.0 / (1.0 + signalStats.Count() * 0.1);
double minAlpha = 1.0 / MathMax(2.0, (double)MathMax(1, hurstPeriod));
alpha = MathMax(minAlpha, alpha); // solo floor, niente max clamp
prevZ = (1.0 - alpha) * prevZ + alpha * zRaw;
double calibrated = CalibrateZ(prevZ);
lastZScore = MathTanh(calibrated);
lastRawSignal = H;
SHARED_regimeH = H;
return lastZScore;
}
void Interact(IAgent *&allAgents[], int count) override {}
void Learn(double predictedZ, double actualReturnZ) override {}
void Save(int fh) const override {
IAgent::Save(fh);
FileWriteDouble(fh, prevZ);
}
void Load(int fh) override {
IAgent::Load(fh);
prevZ = FileReadDouble(fh);
warmup = signalStats.Count(); // ripristina warmup dal conteggio statistiche
}
void Reset() override {
IAgent::Reset();
prevZ = 0;
warmup = 0;
}
string SignalInfo() const override {
return name + " z=" + StringFormat("%+.3f", lastZScore)
+ " H=" + StringFormat("%.3f", SHARED_regimeH)
+ " p=" + (string)hurstPeriod
+ " " + signalStats.ToString();
}
};
#endif