MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user