#ifndef STATISTICS_MQH #define STATISTICS_MQH // Precisione macchina double #define DBL_EPS 2.2204460492503131e-016 // Soglia numerica data-scaled: |x| * DBL_EPS * 1e9 = |x| * 2.22e-7 ≈ |x| * 1e-6 // Epsilon data-driven: sqrt(machine_epsilon) × |x|, mai sotto sqrt(machine_epsilon) // sqrt(DBL_EPS) è la soglia standard per confronti floating-point (Num. Recipes) #define DATA_EPS(x) MathMax(MathSqrt(DBL_EPS), MathAbs(x) * MathSqrt(DBL_EPS)) class EWMA { double value; double alpha; bool init; public: EWMA(double a=0.1) : alpha(a), init(false), value(0) {} void SetAlpha(double a) { alpha = a; } double Alpha() const { return alpha; } double Update(double x) { if(!init) { value = x; init = true; } else value = alpha * x + (1.0 - alpha) * value; return value; } double Value() const { return init ? value : 0; } bool IsInit() const { return init; } void Reset() { init = false; value = 0; } void Save(int fh) const { FileWriteDouble(fh, value); FileWriteInteger(fh, init ? 1 : 0); } void Load(int fh) { value = FileReadDouble(fh); init = FileReadInteger(fh) == 1; } }; class RunningStats { EWMA mean; EWMA var; int minSamples; int count; int freezeAfter; double AdaptiveEpsilon() const { double m = MathAbs(mean.Value()); return (m > 0) ? DATA_EPS(m) : 1e-15; } public: RunningStats(double a=0.05, int minS=20, int freeze=0) : mean(a), var(a), minSamples(minS), count(0), freezeAfter(freeze) {} void SetFreezeAfter(int n) { freezeAfter = n; } bool IsFrozen() const { return freezeAfter > 0 && count >= freezeAfter; } void Update(double x) { if(IsFrozen()) return; if(count == 0) { mean.Update(x); var.Update(0); count = 1; return; } double prevMean = mean.Value(); mean.Update(x); double diff = x - prevMean; var.Update(diff * diff); count++; } double ZScore(double x) { double m = mean.Value(); double s = MathSqrt(var.Value()); if(s < AdaptiveEpsilon() || count < minSamples) return 0; return (x - m) / s; } double RawZScore(double x) { double m = mean.Value(); double s = MathSqrt(var.Value()); if(s < AdaptiveEpsilon()) return 0; return (x - m) / s; } double Mean() const { return mean.Value(); } double Std() const { return MathSqrt(var.Value()); } bool Ready() const { return count >= minSamples; } void Reset() { mean.Reset(); var.Reset(); count = 0; } int Count() const { return count; } void Save(int fh) const { mean.Save(fh); var.Save(fh); FileWriteInteger(fh, count); FileWriteInteger(fh, freezeAfter); } void Load(int fh) { mean.Load(fh); var.Load(fh); count = FileReadInteger(fh); freezeAfter = FileReadInteger(fh); } string ToString() const { string s = "mean=" + StringFormat("%.5f", Mean()) + " std=" + StringFormat("%.5f", Std()) + " n=" + (string)count + "/" + (string)minSamples; if(IsFrozen()) s += " FROZEN"; return s; } }; class RunningCorrelation { double alpha; double meanX, meanY; double cov, varX, varY; int count; int minSamples; double AdaptiveEpsilon() const { double mx = MathAbs(meanX), my = MathAbs(meanY); double ref = (mx + my) * 0.5; return (ref > 0) ? DATA_EPS(ref) : 1e-15; } public: RunningCorrelation(double a=0.05, int minS=10) : alpha(a), minSamples(minS), count(0), meanX(0), meanY(0), cov(0), varX(0), varY(0) {} void Update(double x, double y) { count++; if(count == 1) { meanX = x; meanY = y; return; } double dx = x - meanX; double dy = y - meanY; meanX += alpha * dx; meanY += alpha * dy; double dxNew = x - meanX; double dyNew = y - meanY; cov = (1.0 - alpha) * cov + alpha * dx * dyNew; varX = (1.0 - alpha) * varX + alpha * dx * dxNew; varY = (1.0 - alpha) * varY + alpha * dy * dyNew; } double Correlation() { double denom = MathSqrt(varX * varY); if(denom < AdaptiveEpsilon() || count < minSamples) return 0; double r = cov / denom; double maxObserved = 1.0; return MathMax(-maxObserved, MathMin(maxObserved, r)); } bool Ready() const { return count >= minSamples; } void Reset() { count = 0; meanX = meanY = cov = varX = varY = 0; } int Count() const { return count; } void Save(int fh) const { FileWriteDouble(fh, meanX); FileWriteDouble(fh, meanY); FileWriteDouble(fh, cov); FileWriteDouble(fh, varX); FileWriteDouble(fh, varY); FileWriteInteger(fh, count); } void Load(int fh) { meanX = FileReadDouble(fh); meanY = FileReadDouble(fh); cov = FileReadDouble(fh); varX = FileReadDouble(fh); varY = FileReadDouble(fh); count = FileReadInteger(fh); } }; // --- Kalman Filter Normalizer con Q adattivo --- class KalmanNormalizer { double x; double P; double Q; double R; int count; int minSamples; double AdaptiveEpsilon() const { double ax = MathAbs(x); return (ax > 0) ? DATA_EPS(ax) : 1e-15; } public: KalmanNormalizer(double q=0.001, double r=0.1, int minS=20) : x(0), P(1.0), Q(q), R(r), count(0), minSamples(minS) {} void SetQ(double q) { Q = q; } void SetR(double r) { R = r; } void Update(double obs) { if(count == 0) { x = obs; P = R; count = 1; return; } double innov = obs - x; P += Q; double K = P / (P + R); x += K * innov; P = (1.0 - K) * P; double innovVar = innov * innov; // AdaptRate: innovVar/(R+innovVar) → [0, 1), nessun clamp double adaptRate = innovVar / (R + innovVar); Q = (1.0 - adaptRate) * Q + adaptRate * innovVar; double qMin = DATA_EPS(R); double qMax = R - DATA_EPS(R); // Q < R garantito → K < 0.5 Q = MathMax(qMin, MathMin(qMax, Q)); count++; } double Mean() const { return x; } double Std() const { return MathSqrt(P); } bool Ready() const { return count >= minSamples; } int Count() const { return count; } void Reset() { x = 0; P = 1.0; count = 0; } double ZScore(double obs) { double s = Std(); if(s < AdaptiveEpsilon() || count < minSamples) return 0; return (obs - x) / s; } double RawZScore(double obs) { double s = Std(); if(s < AdaptiveEpsilon()) return 0; return (obs - x) / s; } void Save(int fh) const { FileWriteDouble(fh, x); FileWriteDouble(fh, P); FileWriteInteger(fh, count); } void Load(int fh) { x = FileReadDouble(fh); P = FileReadDouble(fh); count = FileReadInteger(fh); } string ToString() const { return "μ=" + StringFormat("%.5f", x) + " σ=" + StringFormat("%.5f", Std()) + " n=" + (string)count + "/" + (string)minSamples; } }; #endif