#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