#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= targetWindows) break; double sumF2 = 0; int validWin = 0; for(int j=0; j 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= targetWindows) break; double sumRS = 0; int validSub = 0; for(int j=0; j epsVarRS) ? MathSqrt(var) : 0; if(std < MathSqrt(epsVarRS)) continue; double cumDev[]; ArrayResize(cumDev, n); cumDev[0] = returns[base] - mean; for(int k=1; 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 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