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
+303
View File
@@ -0,0 +1,303 @@
# Multi-Agent Trading System - Interaction and Market Logic Documentation
## Agent Interaction Flow
### Overview
The multi-agent system operates through a structured interaction flow where each agent contributes to the final trading decision. This flow is divided into three main phases:
1. **Analysis Phase**: Each agent analyzes market data and produces individual signals
2. **Interaction Phase**: Agents communicate and combine their signals
3. **Orchestration Phase**: The orchestrator makes the final trading decision
### Detailed Interaction Flow
#### Phase 1: Analysis
Each agent independently analyzes the market data during the `Analyze()` method call:
1. **Hurst Agent (RegimeDetector)**:
- Calculates Hurst exponent using DFA/R/S analysis
- Updates `SHARED_regimeH` and `SHARED_regimeZ`
- Period dynamically adjusted based on data length
2. **ADX Agent (RegimeADX)**:
- Calculates ADX using native MT5 function
- Normalizes ADX to z-score
- Updates `SHARED_adxZ` and `SHARED_adxRaw`
3. **MA Agent (MAAgent)**:
- Calculates price-to-MA ratio
- Computes slope of MA
- Combines signals using variance weighting
- Updates `lastZScore` (local, not shared)
4. **Momentum Agent (MomentumAgent)**:
- Calculates momentum and acceleration
- Computes z-scores for both
- Combines signals using variance weighting
- Updates `lastZScore` (local, not shared)
5. **Consensus Agent (RegimeConsensus)**:
- Called by orchestrator during `Interact()` phase
- Combines Hurst and ADX signals
- Calculates agreement between agents
- Updates `SHARED_regimeConsensus`, `SHARED_regimeAgreement`
- Detects trading patterns
- Updates `SHARED_patternCode`, `SHARED_patternName`
6. **Pattern Hunter Agent (PatternHunter)**:
- Called by orchestrator during `Interact()` phase
- Analyzes historical patterns
- Updates `lastZScore` (local, not shared)
#### Phase 2: Orchestration
The orchestrator combines agent signals using a sophisticated softmax gating mechanism:
1. **Signal Collection**:
- Collects z-scores from all agents
- Filters out disabled agents
2. **Softmax Gating**:
- Calculates mean and variance of agent z-scores
- Temperature parameter: `temp = √(zVar) / √(agentCount)`
- Higher variance = higher temperature = more mixing
- Lower variance = lower temperature = more selective
3. **Combined Signal**:
- Applies softmax to get weighted agent contributions
- Applies tanh to normalize to [-1, 1]
- Updates `combinedZ` and `combinedZStats`
#### Phase 3: Market Decision
1. **Entry Signal**:
- Combined z-score compared to `minActionableZ`
- `minActionableZ = combinedZStats.Std()` (empirical)
- Fallback: 0.5 (when stats not ready)
2. **Position Sizing**:
- Base lot: `AdaptiveBaseLot()`
- Adjusted by entry z-score magnitude
- Capped by broker limits
3. **Risk Management**:
- SL: `AdaptiveSL()` = returnStats.Std()
- TP: `AdaptiveTP()` = `AdaptiveSL() * RR ratio`
- RR ratio: `1.0 / (1.0 - winRate)`
## Market Entry/Exit Logic
### Entry Conditions
The system enters a trade when:
1. **Signal Strength**:
- `|combinedZ| > minActionableZ`
- `minActionableZ = combinedZStats.Ready() ? combinedZStats.Std() : 0.5`
- This ensures we only trade when the signal is statistically significant
2. **Position Direction**:
- `dir = 1` if `combinedZ > thr`
- `dir = -1` if `combinedZ < -thr`
- `thr = (overrideMinZ > 0) ? overrideMinZ : AdaptiveMinZ()`
3. **Lot Size Calculation**:
```
riskFrac = wr * (1.0 - MathMin(maxDrawdown, 0.5)) * (0.5 + sharpeBonus * 0.5)
riskFrac = MathMax(0.002, MathMin(0.02, riskFrac))
lot = balance * riskFrac / 100000.0
lot = lot * MathAbs(fs.zScore) // Scale by signal strength
```
### Exit Conditions
#### Take Profit (TP)
- **Trigger**: Price reaches TP level
- **TP Level**: `entryPrice + (atr * tpMult)` (long) or `entryPrice - (atr * tpMult)` (short)
- **tpMult**: `AdaptiveTP() / atr`
#### Stop Loss (SL)
- **Trigger**: Price reaches SL level
- **SL Level**: `entryPrice - (atr * slMult)` (long) or `entryPrice + (atr * slMult)` (short)
- **slMult**: `AdaptiveSL() / atr`
#### Opposite Signal
- **Trigger**: New trade signal in opposite direction
- **Logic**: Close existing position before opening new one
- **Implementation**: Check `CountPositions()` before opening new trade
#### Time-based Exit
- **Maximum Trades**: 20 trades per session
- **Rollback Convergence**: If weights stabilize for `minTrades`, system marks as converged
### Risk Management Details
#### Drawdown Tracking
```
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
if(eq > peakEquity) peakEquity = eq;
double dd = (peakEquity > 0) ? (peakEquity - eq) / peakEquity : 0;
if(dd > maxDrawdown) maxDrawdown = dd;
```
#### Weight Adaptation
```
if(combinedZStats.Ready() && returnStats.Count() > 0) {
double rv = returnStats.Std();
weightAlpha = rv / (1.0 + rv);
weightAlpha = MathMax(minAlpha, weightAlpha);
agents[i].weight = MathMax(weightMin, rho);
}
```
#### Sharpe Bonus Calculation
```
double sharpeBonus = MathMax(0, MathMin(1, rollingSharpe * 0.5));
```
### Market Hours and Session Management
#### Trading Hours
- **Symbol-specific**: Respects each symbol's trading hours
- **No weekend trading**: No trades on weekends
- **Session limits**: Maximum 20 trades per trading session
#### State Management
- **Model persistence**: Saves state at intervals
- **Warm-up**: Uses historical data for initial statistics
- **Recovery**: Loads saved state on startup
## Detailed Agent Interaction Examples
### Example 1: Hurst and ADX Interaction
1. **Hurst Agent Output**:
- `SHARED_regimeH = 0.6` (trending market)
- `SHARED_regimeZ = 0.8` (strong bullish signal)
2. **ADX Agent Output**:
- `SHARED_adxZ = 0.7` (strong trend)
- `SHARED_adxRaw = 35.0` (strong trend strength)
3. **Consensus Agent Processing**:
- `hurstZ = 0.8`, `adxZ = 0.7`
- `hStd = 0.5`, `aStd = 0.5`
- `combinedScale = √(0.5² + 0.5²) = 0.707`
- `agreement = 1.0 - (0.1 / 0.707) = 0.86`
- `SHARED_regimeConsensus = tanh((0.8 + 0.7) / 2 * 0.86) = 0.94`
- Pattern: `strong_trend` (pCode = 1)
### Example 2: Pattern Hunter Interaction
1. **Pattern Hunter Inputs**:
- `hurst = 0.8`, `adx = 0.7`, `ma = 0.3`, `mom = 0.9`
- `tH = 0.5`, `tA = 0.5`, `tM = 0.4`, `tM2 = 0.6`
2. **Pattern Detection**:
- `maMomAgree = (0.3 * 0.9 > 0.4 * 0.6) = true`
- `regimeTrend = (0.8 + 0.7) > 0.707 = true`
- `momAccel = (0.9 > 0.3 + 0.4 * 0.15) && (0.3 > 0.3 + 0.4 * 0.15) = true`
- Pattern: `trend_accel` (pCode = 8)
- `pZ = (0.3 + 0.9) / comboNorm = 1.2 / 0.707 = 1.70`
- `patternStrength = 1.0 - exp(-1.70) = 0.82`
### Example : Market Entry
1. **Combined Signal**:
- `combinedZ = 0.94` (from consensus)
- `minActionableZ = 0.5` (from combinedZStats)
- Entry: `|0.94| > 0.5` ✓
2. **Position Sizing**:
- `balance = 10000`
- `winRate = 0.6`, `maxDrawdown = 0.2`, `sharpeBonus = 0.8`
- `riskFrac = 0.6 * (1 - 0.2) * (0.5 + 0.8 * 0.5) = 0.6 * 0.8 * 0.9 = 0.432`
- `riskFrac = MathMax(0.002, MathMin(0.02, 0.432)) = 0.02`
- `lot = 10000 * 0.02 / 100000 = 0.2`
- `lot = 0.2 * MathAbs(0.94) = 0.188`
3. **SL/TP**:
- `ATR = 0.02`
- `SL = entryPrice - 0.02 * 1.5 = entryPrice - 0.03`
- `TP = entryPrice + 0.02 * 2.5 = entryPrice + 0.05`
## Error Handling and Recovery
### Data Issues
1. **Insufficient Data**:
- Wait for minimum samples before using statistics
- Use fallback values when data insufficient
2. **Division by Zero**:
- Use `DATA_EPS()` for safe division
- Check for zero variance before calculations
3. **Invalid Values**:
- Validate all input data
- Use reasonable defaults for invalid values
### Recovery Mechanisms
1. **Model Reload**:
- Automatic reload on startup if save file exists
- Graceful degradation if save file corrupted
2. **State Reset**:
- Reset all statistics on error
- Log errors for debugging
## Performance Monitoring
### Key Metrics
1. **Trading Performance**:
- Win rate
- Average win/loss ratio
- Maximum drawdown
- Sharpe ratio
2. **System Performance**:
- Agent weight convergence
- Signal strength statistics
- Processing time
- Memory usage
### Health Monitoring
1. **Orchestrator Health**:
- `PrintAgentStatus()`: Shows all agent signals and weights
- `LogHealth()`: Shows rolling performance metrics
- `LogBarHistory()`: Stores historical signals
2. **Agent Health**:
- Each agent has `SignalInfo()` method
- Shows agent name, z-score, and statistics
- Helps identify underperforming agents
## Future Enhancements
1. **Advanced Features**:
- Multi-timeframe analysis
- Machine learning for pattern recognition
- Natural language processing for news sentiment
2. **Risk Management**:
- Dynamic position sizing based on volatility
- Correlation-based risk limits
- Market regime-specific risk adjustments
3. **Monitoring**:
- Real-time performance dashboards
- Automated alert system
- Performance benchmarking
## Conclusion
This documentation provides a comprehensive understanding of the multi-agent trading system's architecture, interaction patterns, and market logic. The system combines multiple analytical approaches to create a robust, data-driven trading strategy that adapts to changing market conditions.
Key strengths:
- **Diversification**: Multiple analytical approaches reduce reliance on single indicators
- **Adaptability**: All parameters are data-driven and adaptive
- **Robustness**: Consensus-based decision making with lateral communication
- **Transparency**: Clear documentation of all agent interactions and logic
The system is designed to be both powerful and maintainable, with clear separation of concerns between different analytical components.
@@ -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
@@ -0,0 +1,90 @@
#ifndef MARKET_DATA_MQH
#define MARKET_DATA_MQH
struct MarketData {
string symbol;
ENUM_TIMEFRAMES timeframe;
int count;
double open[];
double high[];
double low[];
double close[];
double volume[];
datetime time[];
MarketData() {}
MarketData(string sym, ENUM_TIMEFRAMES tf, int cnt) {
symbol = sym;
timeframe = tf;
count = cnt;
ResizeArrays(cnt);
}
void ResizeArrays(int cnt) {
count = cnt;
ArrayResize(open, cnt);
ArrayResize(high, cnt);
ArrayResize(low, cnt);
ArrayResize(close, cnt);
ArrayResize(volume, cnt);
ArrayResize(time, cnt);
}
bool Fetch(int barsBack=0) {
MqlRates rates[];
ArrayResize(rates, count);
ArraySetAsSeries(rates, true);
int copied = CopyRates(symbol, timeframe, barsBack, count, rates);
if(copied != count) {
Print("MarketData::Fetch failed: copied ", copied, " of ", count);
return false;
}
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(volume, true);
ArraySetAsSeries(time, true);
for(int i=0; i<count; i++) {
open[i] = rates[i].open;
high[i] = rates[i].high;
low[i] = rates[i].low;
close[i] = rates[i].close;
volume[i]= (double)rates[i].tick_volume;
time[i] = rates[i].time;
}
return true;
}
double Close(int shift=0) const {
if(shift >= 0 && shift < count) return close[shift];
return 0.0;
}
double High(int shift=0) const {
if(shift >= 0 && shift < count) return high[shift];
return 0.0;
}
double Low(int shift=0) const {
if(shift >= 0 && shift < count) return low[shift];
return 0.0;
}
double TrueRange(int shift=0) const {
if(shift < 0 || shift >= count-1) return 0;
double hl = high[shift] - low[shift];
double hc = MathAbs(high[shift] - close[shift+1]);
double lc = MathAbs(low[shift] - close[shift+1]);
return MathMax(hl, MathMax(hc, lc));
}
double ATR(int period=14) const {
if(count < period + 1) return 0;
double sum = 0;
for(int i=0; i<period; i++) sum += TrueRange(i);
return sum / period;
}
};
#endif
@@ -0,0 +1,838 @@
#ifndef NEURAL_NET_MQH
#define NEURAL_NET_MQH
#include "Statistics.mqh"
class CNeuralNet {
private:
int m_inputs;
int m_hidden;
int m_outputs;
matrix m_W1;
vector m_b1;
matrix m_W2;
vector m_b2;
matrix m_mW1, m_vW1;
vector m_mb1, m_vb1;
matrix m_mW2, m_vW2;
vector m_mb2, m_vb2;
int m_t;
double m_adamBeta1;
double m_adamBeta2;
double m_adamEps;
double m_beta1T;
double m_beta2T;
double m_l2;
int m_epochsTrained;
double m_lastLoss;
bool m_initialized;
// HeInit per ReLU (NeuroBook §1.3)
double HeScale(int fanIn) const {
return MathSqrt(6.0 / MathMax(1, fanIn));
}
// Dropout (NeuroBook §6.2)
double m_dropoutRate;
double m_dropoutMask[];
// BatchNorm semplificata su hidden (NeuroBook §6.3)
// Running stats (EMA) + learnable affine
vector m_bnGamma; // scala learnable
vector m_bnBeta; // shift learnable
vector m_bnRunningMean; // media mobile
vector m_bnRunningVar; // varianza mobile
double m_bnMomentum;
double m_bnEps;
// Debug: loss history
double m_lossHistory[];
string m_lossCsvFn;
void ApplyDropout(vector &h) {
for(int i = 0; i < h.Size(); i++) {
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
h[i] *= m_dropoutMask[i];
}
}
void ApplyBN(vector &h) {
// Normalizza h IN PLACE: per-neuron stats (NeuroBook §6.3)
for(int i = 0; i < h.Size(); i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double standardized = (h[i] - m_bnRunningMean[i]) / denom;
h[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
}
}
// Normalizza E AGGIORNA running stats (training, batch_size=1).
// Usa running stats per normalizzare (stessa modalità di inference) per
// evitare degenerazione con batch=1 (var=0). (NeuroBook §6.3, online BN)
void TrainBN(const vector &h_in, vector &h_out) {
h_out.Resize(h_in.Size());
for(int i = 0; i < h_in.Size(); i++) {
// Update running stats (per-neuron EMA)
m_bnRunningMean[i] = m_bnMomentum * m_bnRunningMean[i] + (1.0 - m_bnMomentum) * h_in[i];
double dx = h_in[i] - m_bnRunningMean[i];
m_bnRunningVar[i] = m_bnMomentum * m_bnRunningVar[i] + (1.0 - m_bnMomentum) * dx * dx;
// Normalize using running stats (same as ApplyBN)
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double standardized = (h_in[i] - m_bnRunningMean[i]) / denom;
h_out[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
}
}
// Backward attraverso BN: dh_out[i] = dh_norm[i] * gamma[i] / sqrt(runningVar[i] + eps)
void BNBackward(vector &dh_out, const vector &h_in, const vector &dh_norm) {
dh_out.Resize(h_in.Size());
for(int i = 0; i < h_in.Size(); i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
dh_out[i] = dh_norm[i] * m_bnGamma[i] / denom;
}
}
double ReLU(double x) const {
return (x > 0.0) ? x : 0.0;
}
double ReLUDeriv(double x) const {
return (x > 0.0) ? 1.0 : 0.0;
}
void Softmax(vector &v) const {
double maxVal = v[0];
for(int i = 1; i < v.Size(); i++)
if(v[i] > maxVal) maxVal = v[i];
double sum = 0.0;
for(int i = 0; i < v.Size(); i++) {
v[i] = MathExp(v[i] - maxVal);
sum += v[i];
}
double eps = DATA_EPS(sum);
if(sum > eps) {
for(int i = 0; i < v.Size(); i++)
v[i] /= sum;
} else {
double eq = 1.0 / v.Size();
for(int i = 0; i < v.Size(); i++)
v[i] = eq;
}
}
void BiasInit(vector &v) {
for(int i = 0; i < v.Size(); i++)
v[i] = 0.0;
}
void ZeroMatrix(matrix &m) {
for(int r = 0; r < (int)m.Rows(); r++)
for(int c = 0; c < (int)m.Cols(); c++)
m[r][c] = 0.0;
}
void ZeroVector(vector &v) {
for(int i = 0; i < v.Size(); i++)
v[i] = 0.0;
}
void AdamUpdate(matrix &param, matrix &m, matrix &v, matrix &grad, double lr) {
for(int r = 0; r < (int)param.Rows(); r++) {
for(int c = 0; c < (int)param.Cols(); c++) {
double g = grad[r][c] + m_l2 * param[r][c];
m[r][c] = m_adamBeta1 * m[r][c] + (1.0 - m_adamBeta1) * g;
v[r][c] = m_adamBeta2 * v[r][c] + (1.0 - m_adamBeta2) * g * g;
double mHat = m[r][c] / (1.0 - m_beta1T);
double vHat = v[r][c] / (1.0 - m_beta2T);
param[r][c] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
}
}
}
void AdamUpdate(vector &param, vector &m, vector &v, vector &grad, double lr) {
for(int i = 0; i < param.Size(); i++) {
double g = grad[i] + m_l2 * param[i];
m[i] = m_adamBeta1 * m[i] + (1.0 - m_adamBeta1) * g;
v[i] = m_adamBeta2 * v[i] + (1.0 - m_adamBeta2) * g * g;
double mHat = m[i] / (1.0 - m_beta1T);
double vHat = v[i] / (1.0 - m_beta2T);
param[i] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
}
}
public:
CNeuralNet()
: m_inputs(0), m_hidden(0), m_outputs(0),
m_t(0), m_beta1T(1.0), m_beta2T(1.0),
m_l2(0), m_epochsTrained(0), m_lastLoss(0.0),
m_initialized(false),
m_dropoutRate(0), m_bnMomentum(0), m_bnEps(0) {}
void Init(int inputs, int hidden, int outputs, double l2 = -1, double dropoutRate = -1) {
m_inputs = inputs;
m_hidden = hidden;
m_outputs = outputs;
// Iperparametri dall'architettura:
// hidden min. = 2 (un neurone non può fare computazione utile)
// β₂/β₁ ratio = 100 (Adam originale: 0.999/0.9)
// BN window = 5× hidden (momentum più lento per layer più grandi)
int totalParams = inputs * hidden + hidden + hidden * outputs + outputs;
int minHidden = MathMax(hidden, 2);
m_l2 = 1.0 / MathMax(totalParams, 1); // L2 = 1/param (1 = nessun parametro → L2=1)
m_dropoutRate = 1.0 / MathSqrt((double)minHidden); // Dropout: 1/sqrt(hidden)
m_adamBeta1 = 1.0 - 1.0 / (double)minHidden; // β₁ = 1 - 1/hidden
m_adamBeta2 = 1.0 - 1.0 / (double)MathMax(minHidden * 100, 200); // β₂ = 1 - 1/(hidden×100)
m_adamEps = DATA_EPS(1.0); // Adam ε: machine epsilon
m_bnMomentum = 1.0 - 1.0 / (double)MathMax(minHidden * 5, 10); // BN momentum = 1 - 1/(hidden×5)
m_bnEps = DATA_EPS(1.0); // BN ε: machine epsilon
// Se chiamata esterna vuole override, usa quelli
if(l2 >= 0) m_l2 = l2;
if(dropoutRate >= 0) m_dropoutRate = dropoutRate;
m_W1.Init(inputs, hidden);
m_b1.Init(hidden);
m_W2.Init(hidden, outputs);
m_b2.Init(outputs);
// He Init per ReLU (NeuroBook §1.3)
{
double scale = HeScale(inputs);
MathSrand(GetTickCount());
for(int r = 0; r < inputs; r++)
for(int c = 0; c < hidden; c++)
m_W1[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
}
BiasInit(m_b1);
{
double scale = HeScale(hidden);
for(int r = 0; r < hidden; r++)
for(int c = 0; c < outputs; c++)
m_W2[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
}
BiasInit(m_b2);
m_mW1.Init(inputs, hidden); ZeroMatrix(m_mW1);
m_vW1.Init(inputs, hidden); ZeroMatrix(m_vW1);
m_mb1.Init(hidden); ZeroVector(m_mb1);
m_vb1.Init(hidden); ZeroVector(m_vb1);
m_mW2.Init(hidden, outputs); ZeroMatrix(m_mW2);
m_vW2.Init(hidden, outputs); ZeroMatrix(m_vW2);
m_mb2.Init(outputs); ZeroVector(m_mb2);
m_vb2.Init(outputs); ZeroVector(m_vb2);
// Dropout mask
ArrayResize(m_dropoutMask, hidden);
// BatchNorm (NeuroBook §6.3)
m_bnGamma.Init(hidden);
m_bnBeta.Init(hidden);
m_bnRunningMean.Init(hidden);
m_bnRunningVar.Init(hidden);
for(int i = 0; i < hidden; i++) {
m_bnGamma[i] = 1.0;
m_bnBeta[i] = 0.0;
m_bnRunningMean[i] = 0.0;
m_bnRunningVar[i] = 1.0;
}
m_t = 0;
m_beta1T = 1.0;
m_beta2T = 1.0;
m_epochsTrained = 0;
m_lastLoss = 0.0;
m_initialized = true;
ArrayResize(m_lossHistory, 0);
m_lossCsvFn = "";
}
void ForwardPass(vector &input, vector &output, vector &h1Cache) {
h1Cache.Resize(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double sum = m_b1[i];
for(int j = 0; j < m_inputs; j++)
sum += input[j] * m_W1[j][i];
h1Cache[i] = ReLU(sum);
}
// BatchNorm inferenza: normalizza con running stats (NeuroBook §6.3)
ApplyBN(h1Cache);
// Dropout scaling in inferenza: scale = 1 - dropoutRate (NeuroBook §6.2)
if(m_dropoutRate > 0) {
for(int i = 0; i < h1Cache.Size(); i++)
h1Cache[i] *= (1.0 - m_dropoutRate);
}
output.Resize(m_outputs);
for(int i = 0; i < m_outputs; i++) {
double sum = m_b2[i];
for(int j = 0; j < m_hidden; j++)
sum += h1Cache[j] * m_W2[j][i];
output[i] = sum;
}
Softmax(output);
}
void Forward(vector &input, vector &output) {
vector h1Cache;
ForwardPass(input, output, h1Cache);
}
double TrainSample(vector &input, vector &target, double lr, double weight = 1.0) {
if(!m_initialized) return -1.0;
// ── Forward ──
// Layer 1: W1*x + b1 → z1 → ReLU → h1_raw → Dropout → h1_drop → BN → h1_norm
vector z1(m_hidden);
vector h1_raw(m_hidden);
for(int i = 0; i < m_hidden; i++) {
z1[i] = m_b1[i];
for(int j = 0; j < m_inputs; j++)
z1[i] += input[j] * m_W1[j][i];
h1_raw[i] = ReLU(z1[i]);
}
// Dropout (NeuroBook §6.2): salva in m_dropoutMask, applica a h1_drop
vector h1_drop(m_hidden);
if(m_dropoutRate > 0.0) {
for(int i = 0; i < m_hidden; i++) {
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
h1_drop[i] = h1_raw[i] * m_dropoutMask[i];
}
} else {
for(int i = 0; i < m_hidden; i++)
h1_drop[i] = h1_raw[i];
}
// BatchNorm (NeuroBook §6.3): normalizza h1_drop → h1_norm, aggiorna running stats
vector h1_cache = h1_drop; // copia per backward
vector h1_norm(m_hidden);
TrainBN(h1_drop, h1_norm);
// Layer 2: W2*h1_norm + b2 → z2 → Softmax → output
vector z2(m_outputs);
for(int i = 0; i < m_outputs; i++) {
z2[i] = m_b2[i];
for(int j = 0; j < m_hidden; j++)
z2[i] += h1_norm[j] * m_W2[j][i];
}
vector output(m_outputs);
for(int i = 0; i < m_outputs; i++) output[i] = z2[i];
Softmax(output);
// ── Loss: CCE pesata + L2 ──
double loss = 0.0;
for(int i = 0; i < m_outputs; i++) {
double p = MathMax(DATA_EPS(output[i]), output[i]);
loss -= weight * target[i] * MathLog(p);
}
loss += 0.5 * m_l2 * (L2Norm(m_W1) + L2Norm(m_W2));
m_lastLoss = loss;
m_t++;
m_beta1T *= m_adamBeta1;
m_beta2T *= m_adamBeta2;
// ── Backward ──
// dL/dz2 = weight * (output - target) (CCE+Softmax combinata, pesata)
vector dL_dz2(m_outputs);
for(int i = 0; i < m_outputs; i++)
dL_dz2[i] = weight * (output[i] - target[i]);
// dL/dW2 = h1_norm ⊗ dL/dz2
matrix dL_dW2(m_hidden, m_outputs);
for(int i = 0; i < m_hidden; i++)
for(int j = 0; j < m_outputs; j++)
dL_dW2[i][j] = h1_norm[i] * dL_dz2[j];
// dL/db2 = dL/dz2
vector dL_db2(m_outputs);
for(int i = 0; i < m_outputs; i++)
dL_db2[i] = dL_dz2[i];
// dL/dh1_norm = W2^T * dL/dz2
vector dL_dh1_norm(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double sum = 0.0;
for(int j = 0; j < m_outputs; j++)
sum += dL_dz2[j] * m_W2[i][j];
dL_dh1_norm[i] = sum;
}
// BatchNorm backward: dL/dh1_drop = gamma/sqrt(var) * dL/dh1_norm
// + update gamma, beta
vector dL_dh1_drop(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double h_std = (h1_cache[i] - m_bnRunningMean[i]) / denom;
double gOld = m_bnGamma[i]; // salva gamma pre-update per backward pass-through
m_bnGamma[i] -= lr * dL_dh1_norm[i] * h_std;
m_bnBeta[i] -= lr * dL_dh1_norm[i];
dL_dh1_drop[i] = dL_dh1_norm[i] * gOld / denom;
}
// Dropout backward: applica stessa mask
if(m_dropoutRate > 0.0) {
for(int i = 0; i < m_hidden; i++)
dL_dh1_drop[i] *= m_dropoutMask[i];
}
// dL/dz1 = dL/dh1_drop * ReLU'(z1)
vector dL_dz1(m_hidden);
for(int i = 0; i < m_hidden; i++)
dL_dz1[i] = dL_dh1_drop[i] * ReLUDeriv(z1[i]);
// dL/dW1 = input ⊗ dL/dz1
matrix dL_dW1(m_inputs, m_hidden);
for(int i = 0; i < m_inputs; i++)
for(int j = 0; j < m_hidden; j++)
dL_dW1[i][j] = input[i] * dL_dz1[j];
// dL/db1 = dL/dz1
vector dL_db1(m_hidden);
for(int i = 0; i < m_hidden; i++)
dL_db1[i] = dL_dz1[i];
// Adam update
AdamUpdate(m_W1, m_mW1, m_vW1, dL_dW1, lr);
AdamUpdate(m_b1, m_mb1, m_vb1, dL_db1, lr);
AdamUpdate(m_W2, m_mW2, m_vW2, dL_dW2, lr);
AdamUpdate(m_b2, m_mb2, m_vb2, dL_db2, lr);
return loss;
}
double L2Norm(matrix &m) {
double sum = 0.0;
for(int r = 0; r < (int)m.Rows(); r++)
for(int c = 0; c < (int)m.Cols(); c++)
sum += m[r][c] * m[r][c];
return sum;
}
// Overload senza pesi (compatibilità)
double Train(matrix &features, matrix &targets, int epochs, double lr) {
vector empty;
return Train(features, targets, epochs, lr, empty);
}
double Train(matrix &features, matrix &targets, int epochs, double lr, vector &weights) {
if(!m_initialized || features.Rows() == 0) return -1.0;
int n = (int)features.Rows();
double avgLoss = 0.0;
ArrayResize(m_lossHistory, epochs);
int logEvery = MathMax(1, epochs / 10);
for(int epoch = 0; epoch < epochs; epoch++) {
int indices[];
ArrayResize(indices, n);
for(int i = 0; i < n; i++) indices[i] = i;
for(int i = n - 1; i > 0; i--) {
int j = MathRand() % (i + 1);
int tmp = indices[i];
indices[i] = indices[j];
indices[j] = tmp;
}
double epochLoss = 0.0;
for(int s = 0; s < n; s++) {
int idx = indices[s];
vector inp = features.Row(idx);
vector tgt = targets.Row(idx);
double w = (weights.Size() > idx) ? weights[idx] : 1.0;
epochLoss += TrainSample(inp, tgt, lr, w);
}
epochLoss /= (double)n;
m_lossHistory[epoch] = epochLoss;
if(epoch == epochs - 1) avgLoss = epochLoss;
if(epoch == 0 || epoch == epochs - 1 || (epoch+1) % logEvery == 0) {
Print(" Epoch ", epoch+1, "/", epochs,
" | loss: ", StringFormat("%.6f", epochLoss),
" | lr: ", StringFormat("%.5f", lr));
}
}
m_epochsTrained += epochs;
m_lastLoss = avgLoss;
return avgLoss;
}
// Salva loss history in CSV nella cartella Common
bool SaveLossCsv(string filename = "") {
if(ArraySize(m_lossHistory) == 0) return false;
if(filename == "") filename = "NN_LossHistory.csv";
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
FileWriteString(fh, "epoch,loss\r\n");
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
FileWriteString(fh, (string)(i+1) + "," + StringFormat("%.8f", m_lossHistory[i]) + "\r\n");
}
FileClose(fh);
Print("Loss history saved to ", filename, " (", ArraySize(m_lossHistory), " epochs)");
return true;
}
string LossHistorySummary() const {
if(ArraySize(m_lossHistory) == 0) return "no history";
double first = m_lossHistory[0];
double last = m_lossHistory[ArraySize(m_lossHistory)-1];
double best = first;
int bestEpoch = 0;
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
if(m_lossHistory[i] < best) { best = m_lossHistory[i]; bestEpoch = i; }
}
return StringFormat("loss: %.6f → %.6f (best: %.6f @ epoch %d, %d epochs)",
first, last, best, bestEpoch+1, ArraySize(m_lossHistory));
}
int Predict(vector &input) {
vector output;
Forward(input, output);
int bestIdx = 0;
double bestVal = output[0];
for(int i = 1; i < m_outputs; i++) {
if(output[i] > bestVal) {
bestVal = output[i];
bestIdx = i;
}
}
return bestIdx;
}
double GetCombinedZ(vector &input) {
vector output;
Forward(input, output);
return output[0] - output[2];
}
bool Save(string filename) {
int fh = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
bool ok = Save(fh);
FileClose(fh);
return ok;
}
bool Save(int fh) {
if(fh == INVALID_HANDLE) return false;
FileWriteInteger(fh, 3);
FileWriteInteger(fh, m_inputs);
FileWriteInteger(fh, m_hidden);
FileWriteInteger(fh, m_outputs);
FileWriteInteger(fh, m_epochsTrained);
FileWriteDouble(fh, m_lastLoss);
FileWriteDouble(fh, m_l2);
FileWriteDouble(fh, m_dropoutRate);
FileWriteDouble(fh, m_bnMomentum);
FileWriteDouble(fh, m_bnEps);
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++)
FileWriteDouble(fh, m_W1[r][c]);
for(int i = 0; i < m_hidden; i++)
FileWriteDouble(fh, m_b1[i]);
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++)
FileWriteDouble(fh, m_W2[r][c]);
for(int i = 0; i < m_outputs; i++)
FileWriteDouble(fh, m_b2[i]);
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++) {
FileWriteDouble(fh, m_mW1[r][c]);
FileWriteDouble(fh, m_vW1[r][c]);
}
for(int i = 0; i < m_hidden; i++) {
FileWriteDouble(fh, m_mb1[i]);
FileWriteDouble(fh, m_vb1[i]);
}
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++) {
FileWriteDouble(fh, m_mW2[r][c]);
FileWriteDouble(fh, m_vW2[r][c]);
}
for(int i = 0; i < m_outputs; i++) {
FileWriteDouble(fh, m_mb2[i]);
FileWriteDouble(fh, m_vb2[i]);
}
FileWriteDouble(fh, m_beta1T);
FileWriteDouble(fh, m_beta2T);
FileWriteInteger(fh, m_t);
// BatchNorm (v3)
for(int i = 0; i < m_hidden; i++) {
FileWriteDouble(fh, m_bnGamma[i]);
FileWriteDouble(fh, m_bnBeta[i]);
FileWriteDouble(fh, m_bnRunningMean[i]);
FileWriteDouble(fh, m_bnRunningVar[i]);
}
return true;
}
bool Load(string filename) {
if(!FileIsExist(filename, FILE_COMMON)) return false;
int fh = FileOpen(filename, FILE_READ | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
bool ok = Load(fh);
FileClose(fh);
return ok;
}
bool Load(int fh) {
if(fh == INVALID_HANDLE) return false;
int version = FileReadInteger(fh);
int inputs = FileReadInteger(fh);
int hidden = FileReadInteger(fh);
int outputs = FileReadInteger(fh);
Init(inputs, hidden, outputs);
m_epochsTrained = FileReadInteger(fh);
m_lastLoss = FileReadDouble(fh);
if(version >= 2) m_l2 = FileReadDouble(fh);
if(version >= 3) {
m_dropoutRate = FileReadDouble(fh);
m_bnMomentum = FileReadDouble(fh);
m_bnEps = FileReadDouble(fh);
}
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++)
m_W1[r][c] = FileReadDouble(fh);
for(int i = 0; i < m_hidden; i++)
m_b1[i] = FileReadDouble(fh);
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++)
m_W2[r][c] = FileReadDouble(fh);
for(int i = 0; i < m_outputs; i++)
m_b2[i] = FileReadDouble(fh);
if(version >= 1) {
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++) {
m_mW1[r][c] = FileReadDouble(fh);
m_vW1[r][c] = FileReadDouble(fh);
}
for(int i = 0; i < m_hidden; i++) {
m_mb1[i] = FileReadDouble(fh);
m_vb1[i] = FileReadDouble(fh);
}
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++) {
m_mW2[r][c] = FileReadDouble(fh);
m_vW2[r][c] = FileReadDouble(fh);
}
for(int i = 0; i < m_outputs; i++) {
m_mb2[i] = FileReadDouble(fh);
m_vb2[i] = FileReadDouble(fh);
}
if(version >= 1) {
m_beta1T = FileReadDouble(fh);
m_beta2T = FileReadDouble(fh);
m_t = FileReadInteger(fh);
}
// BatchNorm (v3)
if(version >= 3) {
m_bnGamma.Init(m_hidden);
m_bnBeta.Init(m_hidden);
m_bnRunningMean.Init(m_hidden);
m_bnRunningVar.Init(m_hidden);
for(int i = 0; i < m_hidden; i++) {
m_bnGamma[i] = FileReadDouble(fh);
m_bnBeta[i] = FileReadDouble(fh);
m_bnRunningMean[i] = FileReadDouble(fh);
m_bnRunningVar[i] = FileReadDouble(fh);
}
}
}
return true;
}
bool IsInitialized() const { return m_initialized; }
int EpochsTrained() const { return m_epochsTrained; }
double LastLoss() const { return m_lastLoss; }
int Inputs() const { return m_inputs; }
int Hidden() const { return m_hidden; }
int Outputs() const { return m_outputs; }
string Info() const {
if(!m_initialized) return "NN: uninitialized";
return StringFormat("NN: %d→%d→%d (epoche=%d, loss=%.6f, dropout=%.2f, BN=%s)",
m_inputs, m_hidden, m_outputs, m_epochsTrained, m_lastLoss,
m_dropoutRate, m_initialized ? "on" : "off");
}
string WeightsSummary() const {
if(!m_initialized) return "NN: uninitialized";
double w1Min = 1e99, w1Max = -1e99, w1Sum = 0;
double w2Min = 1e99, w2Max = -1e99, w2Sum = 0;
int w1Count = 0, w2Count = 0;
for(int r = 0; r < m_inputs; r++) {
for(int c = 0; c < m_hidden; c++) {
double v = m_W1[r][c];
if(v < w1Min) w1Min = v; if(v > w1Max) w1Max = v;
w1Sum += v; w1Count++;
}
}
for(int r = 0; r < m_hidden; r++) {
for(int c = 0; c < m_outputs; c++) {
double v = m_W2[r][c];
if(v < w2Min) w2Min = v; if(v > w2Max) w2Max = v;
w2Sum += v; w2Count++;
}
}
return StringFormat(" W1 [%.4f, %.4f] μ=%.4f | W2 [%.4f, %.4f] μ=%.4f",
w1Min, w1Max, (w1Count>0?w1Sum/w1Count:0),
w2Min, w2Max, (w2Count>0?w2Sum/w2Count:0));
}
};
struct NNTrainSample {
double features[NN_FEATURES];
double target[NN_TARGETS];
double weight; // peso del sample: trades con grosso impatto pesano di più
};
class NNTrainBuffer {
private:
NNTrainSample m_samples[];
int m_count;
public:
NNTrainBuffer() : m_count(0) {}
void Add(double &features[], double &target[], double weight = 1.0) {
int idx = m_count;
ArrayResize(m_samples, idx + 1);
for(int i = 0; i < NN_FEATURES; i++) m_samples[idx].features[i] = features[i];
for(int i = 0; i < NN_TARGETS; i++) m_samples[idx].target[i] = target[i];
m_samples[idx].weight = weight;
m_count++;
}
int Count() const { return m_count; }
void ToMatrices(matrix &features, matrix &targets) {
if(m_count == 0) return;
features.Init(m_count, NN_FEATURES);
targets.Init(m_count, NN_TARGETS);
for(int i = 0; i < m_count; i++) {
for(int j = 0; j < NN_FEATURES; j++)
features[i][j] = m_samples[i].features[j];
for(int j = 0; j < NN_TARGETS; j++)
targets[i][j] = m_samples[i].target[j];
}
}
vector GetWeights() {
vector w(m_count);
for(int i = 0; i < m_count; i++)
w[i] = m_samples[i].weight;
return w;
}
void Clear() {
ArrayResize(m_samples, 0);
m_count = 0;
}
void Trim(int maxSamples) {
if(m_count <= maxSamples) return;
int remove = m_count - maxSamples;
for(int i = 0; i < maxSamples; i++)
m_samples[i] = m_samples[i + remove];
ArrayResize(m_samples, maxSamples);
m_count = maxSamples;
}
// Dump training samples to CSV nella cartella Common per debug
void SaveCsv(string filename = "NN_TrainingSamples.csv") {
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
if(fh == INVALID_HANDLE) return;
FileWriteString(fh, "sample");
for(int f = 0; f < NN_FEATURES; f++) FileWriteString(fh, ",feat_" + (string)f);
for(int t = 0; t < NN_TARGETS; t++) FileWriteString(fh, ",target_" + (string)t);
FileWriteString(fh, ",label\r\n");
for(int i = 0; i < m_count; i++) {
string line = (string)i;
for(int f = 0; f < NN_FEATURES; f++) line += "," + StringFormat("%+.6f", m_samples[i].features[f]);
for(int t = 0; t < NN_TARGETS; t++) line += "," + StringFormat("%.0f", m_samples[i].target[t]);
// Label leggibile
if(m_samples[i].target[0] == 1) line += ",BUY";
else if(m_samples[i].target[2] == 1) line += ",SELL";
else line += ",FLAT";
line += "\r\n";
FileWriteString(fh, line);
}
FileClose(fh);
Print("Training samples saved to ", filename, " (", m_count, " samples)");
}
// Stampa statistiche riassuntive del dataset
void PrintStats() {
int buyCount = 0, sellCount = 0, flatCount = 0;
for(int i = 0; i < m_count; i++) {
if(m_samples[i].target[0] == 1) buyCount++;
else if(m_samples[i].target[2] == 1) sellCount++;
else flatCount++;
}
Print(" Dataset: ", m_count, " samples | Buy: ", buyCount,
" Sell: ", sellCount, " Flat: ", flatCount);
}
// Media feature per classe (utile per vedere se le feature discriminano)
void PrintFeatureStats() {
if(m_count < 3) { Print(" Feature stats: too few samples"); return; }
double meanFeat[NN_FEATURES] = {};
double absMean[NN_FEATURES] = {};
for(int i = 0; i < m_count; i++) {
for(int f = 0; f < NN_FEATURES; f++) {
meanFeat[f] += m_samples[i].features[f];
absMean[f] += MathAbs(m_samples[i].features[f]);
}
}
Print(" Feature means (signal strength per class):");
for(int f = 0; f < NN_FEATURES; f++) {
meanFeat[f] /= m_count;
absMean[f] /= m_count;
}
string labels[NN_FEATURES] = {"Hurst","ADX","MA","Momentum","Consensus","Hunter","Agreement","TrendStr"};
for(int f = 0; f < NN_FEATURES; f++) {
Print(" [", f, "] ", labels[f], ": μ=", StringFormat("%+.4f", meanFeat[f]),
" |avg|=", StringFormat("%.4f", absMean[f]));
}
}
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
#ifndef PERIOD_CALCULATOR_MQH
#define PERIOD_CALCULATOR_MQH
#include "MarketData.mqh"
#include "Statistics.mqh"
class PeriodCalculator {
// Soglia di correlazione: z-score ~2 per significatività approssimata
static double CorrThreshold(int n) { return 2.0 / MathSqrt(MathMax(1, n)); }
public:
static int DominantCycle(const double &close[], int len, int minPeriod=8, int maxPeriod=50) {
if(len < maxPeriod * 2) return (minPeriod + maxPeriod) / 2;
double mean = 0;
for(int i=0; i<len; i++) mean += close[i];
mean /= len;
double variance = 0;
for(int i=0; i<len; i++) variance += (close[i] - mean) * (close[i] - mean);
variance /= len;
if(variance < DATA_EPS(mean)) return (minPeriod + maxPeriod) / 2;
int bestPeriod = (minPeriod + maxPeriod) / 2;
double bestCorr = -999;
for(int p = minPeriod; p <= MathMin(maxPeriod, len/3); p++) {
double cov = 0;
int count = 0;
for(int i=0; i<len-p; i++) {
cov += (close[i] - mean) * (close[i+p] - mean);
count++;
}
cov /= count;
double corr = cov / variance;
double thr = CorrThreshold(count);
if(corr > bestCorr && corr > thr) {
bestCorr = corr;
bestPeriod = p;
}
}
return bestPeriod;
}
// Metodo 2: Efficiency Ratio (Kaufman) - adatta a volatilità
static int EfficiencyRatioPeriod(const double &close[], int len, int minPeriod=5, int maxPeriod=50) {
if(len < 20) return (minPeriod + maxPeriod) / 2;
int lookback = MathMin(len / 3, len - 1);
double totalMove = MathAbs(close[0] - close[lookback]);
double noise = 0;
for(int i=0; i<lookback; i++) noise += MathAbs(close[i] - close[i+1]);
double er = (noise > 0) ? totalMove / noise : 1.0;
int period = minPeriod + (int)((maxPeriod - minPeriod) * (1.0 - er));
return period;
}
// Metodo 3: ATR-based (volatilità recente)
static int ATRPeriod(const MarketData &data, int minPeriod=8, int maxPeriod=40) {
if(data.count < 20) return (minPeriod + maxPeriod) / 2;
int lookback = MathMin(maxPeriod / 3, data.count - 1);
lookback = MathMax(5, lookback);
double atr = 0;
for(int i = 1; i <= lookback; i++) {
double tr = MathMax(data.high[i] - data.low[i],
MathMax(MathAbs(data.high[i] - data.close[i-1]),
MathAbs(data.low[i] - data.close[i-1])));
atr += tr;
}
atr /= lookback;
double avgRange = 0;
for(int i = 0; i < lookback; i++) avgRange += data.high[i] - data.low[i];
avgRange /= lookback;
double volRatio = (avgRange > 0) ? atr / avgRange : 1.0;
int period = minPeriod + (int)((maxPeriod - minPeriod) * volRatio);
return period;
}
// Metodo combinato: pesi data-driven basati sull'accordo tra metodi
static int AutoPeriod(const MarketData &data, int minPeriod=8, int maxPeriod=50) {
int p1 = DominantCycle(data.close, data.count, minPeriod, maxPeriod);
int p2 = EfficiencyRatioPeriod(data.close, data.count, minPeriod, maxPeriod);
int p3 = ATRPeriod(data, minPeriod, maxPeriod);
// Pesi basati sull'accordo: il metodo più vicino alla mediana pesa di più
double sorted[3] = { (double)p1, (double)p2, (double)p3 };
ArraySort(sorted);
double median = sorted[1]; // valore centrale
// Inverse distance weighting: distanza dalla mediana
double d1 = MathAbs(p1 - median);
double d2 = MathAbs(p2 - median);
double d3 = MathAbs(p3 - median);
double w1 = 1.0 / MathMax(0.1, d1);
double w2 = 1.0 / MathMax(0.1, d2);
double w3 = 1.0 / MathMax(0.1, d3);
double sum = w1 + w2 + w3;
double weighted = (p1 * w1 + p2 * w2 + p3 * w3) / sum;
return (int)MathRound(weighted);
}
};
#endif
@@ -0,0 +1,27 @@
#ifndef SIGNAL_MQH
#define SIGNAL_MQH
struct FinalSignal {
int direction; // -1, 0, +1
double confidence; // 0.0 - 1.0 (derivato da |z_combined|)
double zScore; // z-score combinato finale [-1, +1] via tanh
double positionSize; // frazione di capitale suggerita
int agreeingCount;
int totalAgents;
string contributingAgents;
datetime timestamp;
FinalSignal()
: direction(0), confidence(0), zScore(0), positionSize(0),
agreeingCount(0), totalAgents(0), timestamp(0) {}
FinalSignal(int dir, double z)
: direction(dir), confidence(MathAbs(z)), zScore(z),
positionSize(MathAbs(z)), agreeingCount(0), totalAgents(0),
timestamp(TimeCurrent()) {}
bool IsActionable(double minZ=0.5) const {
return direction != 0 && MathAbs(zScore) >= minZ;
}
};
#endif
@@ -0,0 +1,265 @@
#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
@@ -0,0 +1,835 @@
# Documentazione Completa — MultiAgent TR_Agent EA
## Indice
1. [MultiAgentTest.mq5](#1-multiagenttestmq5)
2. [Core/Orchestrator.mqh](#2-coreorchestratormqh)
3. [Core/NeuralNet.mqh](#3-coreneuralnetmqh)
4. [Core/Statistics.mqh](#4-corestatisticsmqh)
5. [Core/Signal.mqh](#5-coresignalmqh)
6. [Core/MarketData.mqh](#6-coremarketdatamqh)
7. [Core/PeriodCalculator.mqh](#7-coreperiodcalculatormqh)
8. [Agents/AgentBase.mqh](#8-agentsagentbasemqh)
9. [Agents/MAAgent.mqh](#9-agentsmaagentmqh)
10. [Agents/MomentumAgent.mqh](#10-agentsmomentumagentmqh)
11. [Agents/PatternHunter.mqh](#11-agentspatternhuntermqh)
12. [Agents/RegimeADX.mqh](#12-agentsregimeadxmqh)
13. [Agents/RegimeConsensus.mqh](#13-agentsregimeconsensusmqh)
14. [Agents/RegimeDetector.mqh](#14-agentsregimedetectormqh)
---
## 1. MultiAgentTest.mq5
### Scopo
Main EA file: punto d'ingresso di MetaTrader 5. Gestisce OnInit (setup), OnTick (per-barra), OnTrade (chiusure), e OnDeinit (salvataggio e report).
### Input Parameters
- **Inp_Symbol**: Simbolo da tradare (vuoto = simbolo corrente)
- **Inp_TF**: Timeframe di esecuzione (default = CURRENT = timeframe del chart)
- Funziona su QUALSIASI timeframe (M1, M5, M15, M30, H1, H4, D1, W1, MN1)
- I periodi degli agenti (Hurst, MA, Momentum, ADX) si adattano automaticamente
- Il buffer dati scala con la barra: 300+ barre sono sempre sufficienti
- L'unica differenza tra timeframes è la granularità degli ingressi
- **Inp_Lot**: Lotto fisso (sostituito da AdaptiveBaseLot se >0)
- **Inp_MinZ**: Soglia minima z-score per entrata (0=adattiva)
- **Inp_WMin**: Peso minimo per agente (0=auto)
- **Inp_BufferBars**: Barre nel buffer (0=auto)
- **Inp_SLRiskATR**: SL fisso in ATR (0=adattivo)
- **Inp_TPRiskATR**: TP fisso in ATR (0=adattivo)
- **Inp_HurstPeriod, Inp_ADXPeriod**: Periodi fissi (0=auto)
- **Inp_UseNeural**: True = neural orchestrator, False = softmax classico
- **Inp_TrainMode**: Colleziona campioni e allena NN in backtest
- **Inp_NNHidden**: Neuroni hidden layer
- **Inp_RiskFraction**: Frazione di capitale da rischiare per trade (default 0.20 = 20%)
- **Inp_UseReversalClose**: Se true, chiude posizioni quando il segnale si inverte. Se false, long e short coesistono
### OnInit
1. Legge parametri di mercato (spread, digits, punto)
2. Crea Orchestrator con gli input
3. Crea e registra 6 agenti: RegimeDetector (Hurst), RegimeADX, RegimeConsensus, MAAgent, MomentumAgent, PatternHunter
4. Verifica buffer dati sufficiente
5. Carica modello NN esistente (se Inp_UseNeural)
### OnTick
- Lavora su **new bar** (compare i tempi)
- Prepara MarketData (prezzi, indicatori)
- Chiama `orchestrator.Analyze(data)` per z-score combinato
- Ottiene FinalSignal da GetFinalSignal()
- Chiama `ManagePositions(data, fs)` per apertura/chiusura
- Trailing stop su tutte le posizioni
### OnTrade
- Rileva trade chiusi da SL/TP (non da reversal)
- Calcola il ritorno in ATR
- Chiama `orchestrator.OnTradeClose()` che:
- Aggiorna MAE/MFE stats
- Colleziona campione NN con peso = |return| + 1
- Chiama agente Learn() con ritorno normalizzato
- Aggiorna health (Sharpe rolling)
- Salva stato ogni saveInterval trade
### ManagePositions
1. Update MAE/MFE per tutti gli aperti (intra-barra high/low)
2. TrailStops: muove SL su tutte le posizioni
3. Chiudi posizioni con segnale invertito (reversal)
4. Aggiorna SL sul terminale via PositionModify
5. Apri nuova posizione OGNI BARRA con |z| > minZ (aggressivo multi-position)
- SL width da AdaptiveSLWidth (MAE-based con EVT fallback)
- Lotto = AdaptiveBaseLot × |z| (scalato dal segnale)
- Nessun TP fisso: uscita via trailing stop o reversal
### OnDeinit
- Salva: stato agenti, correlazioni, RunningStats, NN (inline)
- Stampa report: PrintAgentStats, PrintNeuralStats, loss history CSV
- Aggiorna summary
### Principi Chiave
- **Multi-position**: ogni barra con segnale apre UNA NUOVA posizione
- **Nessun TP fisso**: uscita via reversal o trailing stop
- **SL adattivo**: da distribuzione MAE dei trade vincenti
- **Training online**: campioni collezionati in backtest, NN allenata in OnDeinit
---
## 2. Core/Orchestrator.mqh
### Scopo
Cervello del sistema: orchestrazione agenti, combinazione z-score, gestione multi-position, trailing stop, training NN, stato persistente.
### Strutture Dati
**TrackedTrade**: traccia ogni posizione aperta:
- ticket, entryPrice, entryATR, entryZ, slPrice, isBuy
- highestPrice, lowestPrice (per MAE/MFE tracking)
- maeATR, mfeATR (massimo movimento avverso/favorevole in ATR)
- barsHeld, active
- entryFeatures[NN_FEATURES] (per training NN)
- entryZScores[MAX_AGENTS] (per agent learning)
**BarSnapshot**: cache storica per autocorrelazione:
- combinedZ, timestamp
### Membri Principali
- `agents[MAX_AGENTS]`: array di IAgent*
- `combinedZ`: z-score combinato pesato
- `agentWeights[MAX_AGENTS]`: pesi EWMA basati su correlazione
- `corrMeanX/Y, corrCov, corrVarX/Y`: RunningCorrelation per coppia agente-combinedZ
- `returnStats, combinedZStats, maeStats, mfeStats, maeWinStats`: distribuzioni empiriche
- `openTrades[maxOpenTrades]`: tutte le posizioni aperte
- `m_neuralNet, m_trainBuffer`: rete neurale e buffer training
- `m_riskFraction`: frazione di capitale da rischiare (default 20%)
- `rollingReturns[ROLLING_TRADES]`: per Sharpe rolling
### Metodi Principali
**AddAgent**: registra agente, inizializza correlazioni, ri-inizializza RunningStats con alpha/min derivati da agentCount.
**Analyze** (fase 1-3):
1. **Aggrega**: raccoglie z-score da tutti gli agenti
2. **Interact**: agenti leggono z-score altrui (via SHARED_)
3. **Combina**: pesi EWMA basati su correlazione agente-combinedZ, softmax con temperatura adattiva (o NN)
**OnTradeOpen**: registra nuova posizione in openTracks[], copia entryZScores ed entryFeatures
**OnTradeClose**:
- Calcola ritorno in ATR
- Aggiorna MAE/MFE stats
- Colleziona training sample NN con peso = |return| + 1
- Chiama agent.Learn() per auto-calibrazione
- Aggiorna rolling Sharpe
- Salva stato ogni saveInterval
**UpdateOpenTrades**: per ogni barra, aggiorna MAE/MFE da high/low
**TrailStops**: implementa trailing stop:
- Calcola trail trigger da MFE mean (o EVT fallback)
- Calcola trail offset da MAE win mean (o EVT fallback)
- Se profitto > trigger, muove SL a entry ± (profitto - offset)
**AdaptiveSLWidth**: SL width da MAE distribuzione:
- Fallback: combinedZStats.Std() × sqrt(N) o EVT_SL
- Con dati: max(fallback, maeWinStats.Mean() + maeWinStats.Std())
**AdaptiveTrailTrigger**: da MFE mean (o EVT)
**AdaptiveTrailOffset**: da MAE win mean (o EVT)
**AdaptiveMinZ**: soglia entrata = StdErr(combinedZ) = Std/sqrt(N) — bassa per trading frequente
**AdaptiveBaseLot**: lotto = balance × riskFraction / 100000 — chiamante scala per |z|
**GetTradesToClose**: reversal detection: buy chiusi se currentZ < -minZ, sell se > minZ
**SaveState / LoadState (v6)**:
- Salva: versione, agenti (pesi, bias, predError, predCorr, lastZScore), correlazioni, RunningStats, NN
- NN salvato inline (handle file embedded)
- Carica: stessi campi, ripristino buffer NN
**TrainNN**: allena CNeuralNet con buffer campioni, pesi per sample importance
**InitNeuralNet**: configura architettura NN, inizializza He con iperparametri derivati:
- dropout = 1/sqrt(hidden)
- L2 = 1/max(totalParams, 100)
- β₁ = 1 1/max(hidden, 10)
- β₂ = 1 1/max(hidden×100, 1000)
- BN momentum = 1 1/max(hidden×5, 50)
- BN ε = DATA_EPS(1.0)
### Principi Chiave
- **Zero magic constants**: ogni soglia derivata da distribuzioni empiriche o EVT
- **Multi-position aggressivo**: ogni barra con segnale apre posizione
- **Trailing stop adattivo**: MAE/MFE guidano trigger e offset
- **NN opzionale**: cade a softmax se non inizializzata
- **Persistenza**: tutto (incluso NN) salvato inline in file stato
---
## 3. Core/NeuralNet.mqh
### Scopo
Rete neurale feed-forward: 1 hidden layer ReLU, dropout, BatchNorm, Softmax output, Adam optimizer. Training online con batch_size=1 per compatibilità MT5.
### Architettura
- Input → W1 (fanIn×hidden) + b1 → ReLU → Dropout → BatchNorm → W2 (hidden×3) + b2 → Softmax
- Output: [P(Buy), P(Flat), P(Sell)]
- Hidden size: input Inp_NNHidden (default 6)
### CNeuralNet
**Inizializzazione**: He Init `sqrt(6/fanIn)` per ReLU.
**Forward**:
1. z1 = W1×x + b1 → ReLU(z1) → h1
2. Dropout: mask binaria con probabilità 1-dropoutRate
3. BatchNorm (train): EMA runningMean/runningVar, gamma/beta learnable
4. z2 = W2×h1_norm + b2 → Softmax → output [0,1]³
**Loss**: CCE (Cross-Entropy) pesata + L2:
- L = -Σ weight × target_i × log(p_i) + 0.5×L2×(∥W1∥² + ∥W2∥²)
**Backward (Adam)**:
- dL/dz2 = weight × (output target) (CCE+Softmax combinata)
- dL/dW2 = h1_normᵀ × dL/dz2
- dL/dh1_norm = W2 × dL/dz2
- BNBackward: dL/dh1_drop = gamma/sqrt(var) × dL/dh1_norm
- Dropout backward: passa gradiente solo dove mask=1
- dL/dz1 = dL/dh1_raw ⊙ ReLU'(z1)
- dL/dW1 = xᵀ × dL/dz1
- Adam update per W1, b1, W2, b2, bnGamma, bnBeta
**Iperparametri architettura-derivati** (zero magic constants):
- dropout = 1/sqrt(hidden)
- L2 = 1/max(totalParams, 100)
- β₁ = 1 1/max(hidden, 10)
- β₂ = 1 1/max(hidden×100, 1000)
- ε = DATA_EPS(1.0)
- BN momentum = 1 1/max(hidden×5, 50)
- BN ε = DATA_EPS(1.0)
**Save/Load v3**: pesi salvati in formato binario compatto, inline nello stato.
**LossHistory**: array epoch-loss, dump CSV.
### NNTrainBuffer
Buffer circolare di campioni per training NN.
**Struttura**: NNTrainSample { features[8], target[3], weight }.
**Metodi**:
- Add(features, target, weight): aggiunge campione
- Trim(maxSamples): rimuove i più vecchi
- ToMatrices(features, targets): esporta per training
- GetWeights(): vettore dei pesi campione
- SaveCsv(filename): dump per debug
- PrintStats/PrintFeatureStats: diagnostica dataset
### Principi Chiave
- **Batch size = 1**: forzato da MT5, BN usa running stats non batch stats
- **Loss pesata**: trade con grande impatto pesano di più nel training
- **Inline nel file stato**: NN salvato dentro .model per portabilità tester→live
- **Zero magic**: dropout, L2, β₁, β₂, ε, BN momentum/ε tutti derivati
---
## 4. Core/Statistics.mqh
### Scopo
Statistiche in tempo reale: EWMA, RunningStats (media/variance online), RunningCorrelation, DATA_EPS.
### DATA_EPS(x)
- `MathMax(MathSqrt(DBL_EPS), MathAbs(x) * MathSqrt(DBL_EPS))`
- Soglia floating-point di qualità: scala con la magnitudine dei dati
- Radice quadrata del DBL_EPS standard (≈1.49e-8), non epsilon assoluto 1e-15
### EWMA
EWMA univariato con fattore α:
- mean += α × (x mean)
- var = (1−α) × (var + α × (xmean)²) (metodo di Finch)
### RunningStats
Media e varianza online con EWMA:
- alpha = 1/(N_agenti × 10), minSamples = N_agenti × 5
- Count(), Mean(), Std(), Var(), Skew()
- Ready(): count >= minSamples
- Save/Load: formato binario
### RunningCorrelation
Correlazione online tra due variabili:
- Stessa logica EWMA di RunningStats
- Update(x, y), Correlation(), Count(), Ready()
- Utilizzata per: correlazione agente-combinedZ, weight update
### Funzioni EVT
- `EVT_MaxAbsZ(N) = sqrt(2 × log(2 × N))`: expected maximum |z| di N gaussiane
- `RStatsAlpha() = 1.0 / max(agentCount × 10, 10)`
- `RStatsMinSamp() = max(agentCount × 5, 10)`
### Principi Chiave
- **Online**: tutte le statistiche sono incrementali, nessun buffer storico
- **EWMA**: pesa più i dati recenti
- **DATA_EPS**: soglia scalabile, non epsilon assoluto
- **EVT**: Extreme Value Theory per fallback senza dati
---
## 5. Core/Signal.mqh
### Scopo
Strutture dati di segnale e stato.
### FinalSignal
Rappresenta il segnale finale dopo orchestrazione:
- direction: +1 (Buy), -1 (Sell), 0 (Flat)
- zScore: z-score combinato (firmato)
- totalAgents: numero agenti attivi
- agreeingCount: quanti agenti concordano con direzione
- contributingAgents: stringa nomi agenti concordanti
### TrackedTrade
Rappresenta una posizione aperta tracciata:
- ticket, entryPrice, entryATR, entryZ, slPrice, isBuy
- highestPrice, lowestPrice: per MAE/MFE intra-barra
- maeATR, mfeATR: massimo movimento avverso/favorevole in ATR
- barsHeld: barre da apertura
- active: true se posizione ancora aperta
- entryFeatures[8]: feature snapshot all'apertura (per NN)
- entryZScores[MAX_AGENTS]: z-score agenti all'apertura (per learning)
### BarSnapshot
- combinedZ, timestamp: per calcolo autocorrelazione
### Principi Chiave
- **TrackedTrade è auto-contenuta**: porta tutto ciò che serve per learning e training
- **Multi-position**: array di TrackedTrade fino a maxOpenTrades
---
## 6. Core/MarketData.mqh
### Scopo
Fornitore dati di mercato: prezzi, spread, indicatori nativi MT5.
### MarketData
- Symbol() / Timeframe()
- High(shift), Low(shift), Close(shift), Open(shift)
- Spread(): spread corrente in punti
- ATR(period): iATR nativo
- ATR(period, shift): iATR con shift
- Ma(period, shift, method, applied): iMA nativo
- Momentum(period, shift): iMomentum nativo
- ADX(period, shift): iADX nativo (Main, PlusDI, MinusDI)
### Principi Chiave
- **Indicatori nativi MT5**: iMA, iATR, iMomentum, iADX — nessuna implementazione custom
- **Cache**: i valori sono calcolati su richiesta con handle MT5
- **ATR period derivato**: max(N+1, min(buffer/15, 50)) — scala con agenti e buffer
---
## 7. Core/PeriodCalculator.mqh
### Scopo
Calcolo dei periodi adattivi per ogni agente.
### Funzioni
- `HurstPeriod(bufferBars, hurstPeriodInput)`: periodo per calcolo Hurst
- `ADXPeriod(bufferBars, adxPeriodInput)`: periodo per ADX
- `MAPeriod(bufferBars, ...)`: periodo per MA
- `MomentumPeriod(bufferBars, ...)`: periodo per Momentum
Ogni funzione:
- Se input > 0: usa input fisso
- Se input = 0: calcola da bufferBars usando formula adattiva
---
## 8. Agents/AgentBase.mqh
### Scopo
Classe base astratta per tutti gli agenti. Definisce interfaccia, stato persistente, meccanismi di calibrazione.
### IAgent (interfaccia)
```
virtual void Analyze(MarketData&) = 0;
virtual void Learn(double actualReturn) = 0;
virtual double GetZScore() = 0;
```
### Membro
- name, enabled, weight, lastZScore
- predictionError: RunningStats dell'errore di predizione (bias)
- predCorr: RunningCorrelation tra predetto e reale
- combinedZStats: RunningStats locale per normalizzare z-score
### AgentBase (implementazione parziale)
**CalibrateZ(z)**: normalizza z-score usando combinedZStats locale:
- Se ha dati: z_score = (z mean) / std
- Se non ha dati: z_score = z / EVT_MaxAbsZ(max(2N, 2))
**Learn(actualReturn, subSignals)**:
- Calcola predictionError: differenza tra lastZScore e actualReturn normalizzato
- Aggiorna predCorr: correla lastZScore con actualReturn
- Tutti gli agenti ereditano questo comportamento
**Save/Load v3**: salva nome, peso, lastZScore, predictionError, predCorr, combinedZStats.
### Principi Chiave
- **Auto-calibrazione**: ogni agente normalizza i propri z-score
- **Autovalutazione**: predictionError (bias) + predCorr (allineamento direzionale)
- **Zero magic**: fallback EVT per normalizzazione
---
## 9. Agents/MAAgent.mqh
### Scopo
Agente Moving Average: confronta prezzo corrente con MA per generare z-score.
### Funzionamento
1. Calcola MA a periodo adattivo (iMA nativo)
2. Prezzo relativo: (close MA) / ATR
3. Normalizza via CalibrateZ() per produrre z-score ~N(0,1)
4. Learn(): traccia sub-signals priceToMaCorr e slopeCorr
### Sub-signals
- priceToMaCorr: RunningCorrelation tra (close-MA)/ATR e ritorno
- slopeCorr: RunningCorrelation tra pendenza MA e ritorno
### Output
- Z-score positivo: prezzo sopra MA (trend rialzista)
- Z-score negativo: prezzo sotto MA (trend ribassista)
- Segnale tanto più forte quanto più il prezzo è lontano dalla MA (in ATR)
### Principi Chiave
- **MA period adattivo**: da PeriodCalculator
- **Unità ATR**: normalizza la distanza prezzo-MA in termini di volatilità
- **Auto-calibrazione**: CalibrateZ mantiene output ~N(0,1)
- **Apprendimento**: traccia correlazione sub-signals per miglioramento
---
## 10. Agents/MomentumAgent.mqh
### Scopo
Agente Momentum: cattura accelerazione del prezzo via iMomentum nativo.
### Funzionamento
1. Calcola Momentum = iMomentum(period) = close close[period-1]
2. Momento normalizzato: momentum / ATR (in unità di volatilità)
3. Seconde differenze per accelerazione: momentum momentum[shift]
4. Combina momentum + accel con RunningStats pesato
5. CalibrateZ() per z-score finale
### Sub-signals
- momCorr: RunningCorrelation tra momentum e ritorno
- accelCorr: RunningCorrelation tra accelerazione e ritorno
### Output
- Z-score positivo: momentum positivo (trend in accelerazione)
- Z-score negativo: momentum negativo
- Pesa accelerazione quando ha alta correlazione col ritorno
### Principi Chiave
- **iMomentum nativo MT5**: no implementazione custom
- **Doppio segnale**: momentum + accelerazione
- **Adattivo**: peso accelerazione basato su correlazione storica
---
## 11. Agents/PatternHunter.mqh
### Scopo
Agente di pattern recognition cross-agente: identifica pattern negli z-score degli altri agenti.
### Funzionamento
1. Analizza z-score di tutti gli agenti (da SHARED_ variabili)
2. Classifica pattern in codici -10..+10:
- Pattern di accordo: tutti concordi (z-std basso)
- Pattern di divergenza: agenti divisi (z-std alto)
- Pattern di reversal: cambio direzione rispetto a barra precedente
- Pattern di momentum: accelerazione/decelerazione
3. Ogni pattern code ha RunningStats locale (patternStats[code+10])
4. Z-score = winRate del pattern corrente (CalibrateZ normalizza)
### Learn()
- patternStats[patternCode+10] aggiornato con esito trade
- Traccia win rate per ogni pattern
### Output
- Timeframe lungo: convergenza/dominanza di certi pattern
- Z-score positivo: pattern storicamente vincente per Buy
- Z-score negativo: pattern storicamente vincente per Sell
### Principi Chiave
- **Cross-agente**: non guarda il prezzo ma il comportamento degli altri agenti
- **Pattern statistics**: ogni pattern ha RunningStats di performance
- **Auto-apprendimento**: più trade → migliori stime win rate per pattern
---
## 12. Agents/RegimeADX.mqh
### Scopo
Agente di regime basato su ADX: rileva trend vs range tramite iADX nativo.
### Funzionamento
1. iADX(period, shift): ADX Main, +DI, -DI
2. Regime score = (+DI -DI) / ADX_Main (direzione normalizzata per forza trend)
3. CalibrateZ() per output ~N(0,1)
### Output
- Z-score positivo: +DI > -DI → trend rialzista
- Z-score negativo: -DI > +DI → trend ribassista
- Magnitudo: forza del trend (ADX alto) + direzionalità
### Principi Chiave
- **iADX nativo MT5**: no implementazione custom
- **Unità di forza trend**: regime score normalizzato da ADX
- **Risposta rapida**: reagisce a cambiamenti di regime DI
---
## 13. Agents/RegimeConsensus.mqh
### Scopo
Agente di consenso: combina Hurst + ADX per determinazione robusta del regime.
### Funzionamento
1. Legge Hurst da RegimeDetector (via SHARED_)
2. Legge ADX da RegimeADX (via SHARED_)
3. Consensus score = peso × Hurst + (1-peso) × ADX
4. Peso da RunningStats della correlazione di ciascuno col ritorno
5. CalibrateZ() per output finale
### Learn()
- Sub-signals: hurstCorr (corr Hurst-ritorno), adxCorr (corr ADX-ritorno)
- Aggiorna pesi basati su correlazione storica
### Output
- Z-score positivo: regime identificato come trend rialzista
- Z-score negativo: regime trend ribassista
- Zero: regime incerto (Hurst range-bound + ADX basso)
### Principi Chiave
- **Consenso multi-regime**: Hurst + ADX complementari
- **Pesi adattivi**: aggiornati via Learn() basato su performance
- **Stabilità**: consenso filtra falsi segnali di un singolo regime detector
---
## 14. Agents/RegimeDetector.mqh
### Scopo
Agente di detection basato su Hurst Exponent: misura tendenza al trend/mean-reversion del mercato.
### Funzionamento
1. Hurst via DFA (Detrended Fluctuation Analysis) con fallback R/S
2. Hurst = 0.5 → random walk
3. Hurst > 0.5 → trending (buy quando close sopra MA, sell sotto)
4. Hurst < 0.5 → mean-reverting (buy quando close sotto MA, sell sopra)
5. Z-score = (Hurst - 0.5) × factor × (close-MA)/ATR
6. CalibrateZ() per output finale
### DFA Algorithm
1. Prezzo logaritmico: y_i = log(close_i)
2. Trend locale: sottrai media su finestra mobile
3. Fluttuazione: RMS dei residui
4. Hurst = log(F(n) / F(n/2)) / log(2)
### Fallback R/S
- Se DFA fallisce (dati insufficienti): R/S classico
- Range = max(cumsum) min(cumsum)
- R/S = range / std dei log-returns
### Output
- Z-score positivo: mercato trending rialzista
- Z-score negativo: mercato trending ribassista
- Magnitudo: forza del fenomeno (quanto Hurst si discosta da 0.5 × forza trend)
### Principi Chiave
- **Implementazione manuale**: nessun indicatore nativo (Hurst non esiste in MT5)
- **DFA primario, R/S fallback**: robustezza
- **Direzionalità**: combina Hurst con posizione prezzo-MA
- **Lento ma fondamentale**: identifica regime macro, non trading frequency
---
## Filosofia Generale del Sistema
### Multi-Agente con Orchestrazione Neurale
6 agenti specializzati producono z-score normalizzati ~N(0,1). L'orchestratore combina:
- Classic: pesi EWMA basati su correlazione storica + softmax con temperatura adattiva
- Neural: CNeuralNet che impara la combinazione non-lineare ottimale
### Zero Magic Constants
Ogni numero nel sistema è derivato da:
- **Distribuzioni empiriche**: RunningStats di MAE/MFE/ritorni
- **EVT (Extreme Value Theory)**: fallback senza dati per SL, trigger, offset
- **Architettura NN**: dropout, L2, β₁, β₂, ε da hidden size
- **Floating-point standard**: sqrt(DBL_EPS) per soglie numeriche
- **Statistica classica**: StdErr, correlazioni, Student-t
### Multi-Position Aggressivo
- Ogni barra con segnale apre nuova posizione
- 20% di capitale per trade (configurabile)
- Nessun TP fisso: trailing stop adattivo da MFE/MAE
- SL width da MAE dei trade vincenti
### Apprendimento Continuo
- Agenti: CalibrateZ() + Learn() con bias/correlation tracking
- NN: training supervisionato con campioni pesati per importanza
- Stato: tutto salvato inline (incluso NN) per portabilità tester→live
---
## 15. Trade Entry / Exit Logic
### Come Entra a Mercato
L'entry avviene in `ManagePositions()` in MultiAgentTest.mq5, **ogni barra** con segnale actionable:
1. **Nuova barra rilevata** in OnTick → chiama `orchestrator.Analyze(data)` per produrre `combinedZ`
2. **GetFinalSignal()** produce `FinalSignal { direction, zScore, ... }`
3. **Controllo soglia**: se `|zScore| > minZ` (dove `minZ = AdaptiveMinZ()`), si procede
4. **Calcolo SL**: `SL = price ± atr × slWidth` dove `slWidth = AdaptiveSLWidth()`
5. **Calcolo lotto**: `lot = AdaptiveBaseLot() × |zScore|`
- `AdaptiveBaseLot() = balance × riskFraction / 100000`
- Con riskFraction = 0.20 (20%): per $10k → lotto base 0.20 × z
6. **Chiamata MT5**: `trade.Buy(lot, sym, ask, sl, 0)` o `trade.Sell(lot, sym, bid, sl, 0)`
- TP = 0: nessun take profit fisso, l'uscita è gestita da trailing stop o reversal
7. **Tracciamento**: `orchestrator.OnTradeOpen(ticket, price, atr)` salva la posizione in `openTrades[]`
### Frequenza di Entry (Multi-Position)
- **Ogni barra** con segnale apre UNA NUOVA posizione, indipendentemente da quante sono già aperte
- **Solo il margine libero** limita il numero di posizioni: se `marginReq ≥ freeMargin`, il trade è saltato
- Nessun limite artificiale: l'array `openTrades[]` cresce dinamicamente (`EnsureTradeCapacity`)
- Con `AdaptiveMinZ = Std/sqrt(N)` (~0.16 con 6 agenti), la soglia è molto bassa → molte entrate
- Il sistema può gestire centinaia di posizioni contemporanee se il margine lo permette
### Come Esce a Mercato
Ci sono **tre modi** per chiudere una posizione:
**A) Reversal (chiusura attiva) — OPZIONALE**
- Controllato da `Inp_UseReversalClose` (default: true)
- Se **true**: in `ManagePositions()`, dopo aver calcolato il nuovo segnale:
- `GetTradesToClose()` ritorna i ticket delle posizioni con direzione opposta al segnale corrente
- BUY chiusi se `currentZ < -minZ`, SELL chiusi se `currentZ > minZ`
- Chiamata: `OnTradeClose(tkt, closePrice)` + `trade.PositionClose(tkt)`
- Se **false**: le posizioni NON vengono mai chiuse per reversal. Ogni barra apre UNA NUOVA posizione nella direzione del segnale, e le posizioni esistenti rimangono aperte. Questo permette:
- **Long e short simultanei**: il sistema può tenere posizioni in entrambe le direzioni
- **Massimizzazione del profitto**: non taglia le posizioni che potrebbero ancora essere redditizie
- **Uscita solo via trailing stop o SL**: nessuna chiusura "forzata" dal segnale
- Attenzione: il rischio aumenta perché le posizioni opposte si compensano parzialmente
**B) Trailing Stop (chiusura automatica)**
- In `TrailStops()`, chiamato ogni barra:
1. Calcola **trail trigger**: da `AdaptiveTrailTrigger()` = MFE medio o EVT fallback
2. Calcola **trail offset**: da `AdaptiveTrailOffset()` = MAE medio dei vincenti o EVT fallback
3. Se il profitto corrente > trail trigger (in ATR):
- BUY: `newSL = entryPrice + (highestPrice - entryPrice) - offset`
- SELL: `newSL = entryPrice - (entryPrice - lowestPrice) + offset`
4. Se il nuovo SL è più stretto del precedente, viene aggiornato via `PositionModify(tkt, newSL, curTP)`
- Il trailing stop si attiva SOLO dopo che il prezzo ha superato il tipico MFE
**C) SL/TP iniziale (broker-side)**
- All'apertura, ogni posizione ha uno stop loss (SL = price ± atr × slWidth)
- Se il mercato va contro la posizione, lo SL iniziale ferma la perdita
- La chiusura da SL/TP è rilevata in `OnTrade()`: se un ticket tracciato non è più in `PositionsTotal()`, viene chiamato `OnTradeClose()`
- Il trailing stop può spostare lo SL per proteggere i profitti, ma non lo allarga mai
### Numero di Ordini
- **Entrate**: 1 posizione per barra con segnale (potenzialmente centinaia al giorno su M1)
- **Uscite**: 1 chiusura per posizione (via reversal, trailing, o SL)
- **Modifiche**: 1 modifica per barra per posizione (trailing SL via PositionModify)
- **Totale ordini**: dipende dalla frequenza dei segnali × numero posizioni aperte
- In backtest su H1 con 6 agenti: tipicamente 50-200 trade/mese, ma con entry aggressive può essere molto di più
---
## 16. Risk Management
### Dimensionamento Posizione
Il rischio per trade è controllato da due livelli:
**Livello 1: Frazione di Capitale (`Inp_RiskFraction`)**
- Default: 0.20 (20% del capitale per trade)
- `lotto = balance × riskFraction / 100000 × |z|`
- Esempio: $10,000 × 0.20 / 100000 × |z=1.0| = 0.02 lotti (= 2,000 unità EURUSD)
- Il `|z|` scala il lotto in base alla forza del segnale (|z|=2 → lotto doppio)
**Livello 2: Stop Loss Iniziale**
- `SL = prezzo ± atr × slWidth`
- `slWidth = AdaptiveSLWidth()`: usa MAE dei trade vincenti (o EVT fallback)
- Maggiore slWidth → stop più largo → maggiore rischio per trade
- Il trailing stop protegge ulteriormente spostando lo SL in profitto
**Livello 3: Margine**
- Controllo esplicito: `if(marginReq ≥ freeMargin) return`
- Se non c'è abbastanza margine per il lotto calcolato, il trade è saltato
- Questo è l'UNICO limite reale al numero di posizioni
### Distribuzione del Rischio (Multi-Position)
Con multi-position:
- Ogni posizione ha rischio individuale = `balance × riskFraction × |z| / 100000`
- Con 10 posizioni contemporanee: rischio totale = 10 × 0.20 × |z_medio| / 100000 × balance
- Il rischio totale è proporzionale a: `Σ|z_i| × riskFraction × balance / 100000`
- La diversificazione tra N posizioni riduce il rischio specifico di ogni entry
### Drawdown Control
Il sistema non ha un hard stop sul drawdown (per scelta: l'utente vuole trading aggressivo). Tuttavia:
- Il trailing stop limita le perdite di ogni singola posizione
- La distribuzione MAE dei vincenti guida la larghezza dello SL
- Il `rollingSharpe` negativo è segnalato ma non blocca il trading
---
## 17. Manual Override System
Anche se il sistema è progettato per essere "zero magic constants" con tutti i valori derivati dai dati, puoi **sovrascrivere manualmente** qualsiasi parametro tramite gli **input dell'EA**:
### Cosa si può sovrascrivere
| Input | Default | Descrizione |
|-------|---------|-------------|
| `Inp_MinZ` | 0.0 (auto) | Soglia minima z-score. Se >0, ignora `AdaptiveMinZ()` |
| `Inp_WMin` | 0.0 (auto) | Peso minimo per agente. Se >0, ignora `1/(N×sqrt(N))` |
| `Inp_SLRiskATR` | 0.0 (auto) | SL fisso in ATR. Se >0, ignora `AdaptiveSLWidth()` |
| `Inp_TPRiskATR` | 0.0 (auto) | TP fisso in ATR. (TP=0 = trailing stop puro) |
| `Inp_HurstPeriod` | 0 (auto) | Periodo Hurst fisso. Se >0, ignora calcolo adattivo |
| `Inp_ADXPeriod` | 0 (auto) | Periodo ADX fisso. Se >0, ignora calcolo adattivo |
| `Inp_Lot` | 0.01 | Lotto fisso. Sostituisce `AdaptiveBaseLot()` se >0 |
| `Inp_BufferBars` | 0 (auto) | Numero barre buffer. Se 0: `max(300, HurstPeriod×10)` |
| `Inp_RiskFraction` | 0.20 | Frazione di capitale per trade. Puoi abbassare (0.05=5%) o alzare (0.50=50%) |
| `Inp_UseNeural` | false | True = neural orchestrator, False = softmax classico |
| `Inp_TrainMode` | false | Colleziona campioni e allena NN in backtest |
| `Inp_NNHidden` | 6 | Neuroni hidden layer della NN |
| `Inp_UseReversalClose` | true | true=chiudi su segnale opposto, false=long+short coesistono |
### Come funziona l'override
Nel codice, ogni input segue il pattern:
```mql
// Se input > 0, usa input. Se = 0, usa valore adattivo
double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ();
```
Oppure per parametri non-zero:
```mql
// NN: se passato >= 0, override
if(l2 >= 0) m_l2 = l2;
if(dropoutRate >= 0) m_dropoutRate = dropoutRate;
```
### Esempi Pratici
**Scenario conservativo**: `Inp_RiskFraction=0.05, Inp_MinZ=1.0`
- Rischia 5% per trade, soglia alta → pochi trade selezionati
**Scenario aggressivo**: `Inp_RiskFraction=0.50, Inp_MinZ=0`
- Rischia 50% per trade, soglia adattiva bassa → trade frequenti e grandi
**Scenario fisso**: `Inp_SLRiskATR=2.0, Inp_Lot=0.10`
- SL sempre a 2 ATR, lotto fisso 0.10 — nessun adattamento
### Cosa NON si può sovrascrivere (rimane data-driven)
- RunningStats alpha/min: derivati da N agenti
- NN iperparametri: dropout, L2, β₁, β₂, ε — derivati dall'architettura
- MAE/MFE distribuzioni: sempre empiriche
- EVT fallback: solo quando non ci sono dati
- Correlazioni e pesi: sempre empirici
---
## 18. Dati Salvati per Analisi Periodica
Alla fine del backtest (`OnDeinit`), il sistema salva MOLTEPLICI file per analisi approfondita:
### File Salvati
| File | Contenuto | Cartella |
|------|-----------|----------|
| `TR_Agent_Analysis_SYMBOL_TF.csv` | **TUTTI** i parametri derivati, soglie, periodi, stats | `Common` |
| `TR_Agent_NN_v1.dat` | Modello NN con pesi inline | `Common` |
| `NN_TrainingSamples_SYMBOL_TF.csv` | Campioni di training (8 feature + target) | `Common` |
| `NN_LossHistory_SYMBOL_TF.csv` | Loss per epoca di training | `Common` |
| `AgentLearning_SYMBOL_TF.csv` | Bias, correlazioni, errori per ogni agente | `Common` |
### Contenuto del CSV di Analisi (`SaveAnalysisCSV`)
Il file `TR_Agent_Analysis_SYMBOL_TF.csv` contiene TUTTO:
**Sezione 1: Trade Statistics**
- TotalTrades, WinCount, WinRate, MaxDrawdown, RollingSharpe
- ReturnMean, ReturnStd, ReturnCount
- CombinedZMean, CombinedZStd, CombinedZCount
**Sezione 2: MAE/MFE Distributions**
- MAE_Mean, MAE_Std (massimo movimento avverso)
- MFE_Mean, MFE_Std (massimo movimento favorevole)
- MAE_Win_Mean, MAE_Win_Std (MAE dei soli vincenti)
**Sezione 3: Derived Thresholds**
- MinZ (soglia di entrata usata)
- SLWidth (larghezza SL in ATR)
- TrailTrigger (quando si attiva il trailing)
- TrailOffset (quanto si arretra lo SL)
- BaseLot (lotto base calcolato)
- CorrMinSamples, WeightMin, WeightAlpha
- EVT_MaxAbsZ
**Sezione 4: Agent Details**
- Per ogni agente: Name, Weight, Rho (correlazione), LastZ, Bias, BiasN, RhoLearn, RhoLearnN
**Sezione 5: Neural Network**
- NN_Epochs, NN_Loss, NN_Arch, NN_LR
**Sezione 6: Input Parameters** (per poter replicare il backtest)
- Tutti gli Inp_ parametri come sono stati usati
### Periodicità del Salvataggio
- **Stato modello**: salvato ogni `saveInterval = N × 5` trade chiusi da SL/TP
- **Analisi CSV**: salvata UNA volta in `OnDeinit`
- **Agent Learning CSV**: salvato UNA volta in `OnDeinit`
- **NN auto-save**: ogni 50 campioni durante il training (se TrainMode)
- **NN Loss CSV**: salvato dopo ogni training in `OnDeinit`
### Come Usare i CSV per Analisi
1. **Dopo backtest**: apri `Common\TR_Agent_Analysis_SYMBOL_TF.csv` in Excel/Google Sheets
2. Controlla: Sharpe, Win Rate, Max Drawdown, MAE/MFE
3. Confronta soglie derivate tra backtest diversi per vedere l'evoluzione
4. Usa `NN_TrainingSamples_SYMBOL_TF.csv` per analizzare la distribuzione delle feature
5. Confronta `NN_LossHistory` per verificare che la loss scenda durante il training
6. Porta il `.dat` in live e ricarica lo stato esattamente com'era in backtest
@@ -0,0 +1,435 @@
#property copyright "MultiAgent Test v7"
#property version "7.00"
#property description "6 agenti: Hurst, ADX, Consensus, MA, Momentum, Hunter"
#property description "Neural orchestrator (CNeuralNet) + Pattern hunter cross-agente"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include "Core\Orchestrator.mqh"
#include "Agents\RegimeDetector.mqh"
#include "Agents\RegimeADX.mqh"
#include "Agents\RegimeConsensus.mqh"
#include "Agents\PatternHunter.mqh"
#include "Agents\MAAgent.mqh"
#include "Agents\MomentumAgent.mqh"
input string Inp_Symbol = "";
input ENUM_TIMEFRAMES Inp_TF = PERIOD_CURRENT; // CURRENT = timeframe del chart
input double Inp_Lot = 0.01;
input double Inp_MinZ = 0.0; // 0=adattivo, >0=fisso
input double Inp_WMin = 0.0; // 0 = auto (1/agentCount * 0.1)
input int Inp_BufferBars = 0; // 0 = auto (HurstMaxPeriod * 10)
input double Inp_SLRiskATR = 0.0; // 0=adattivo, >0=fisso
input double Inp_TPRiskATR = 0.0; // 0=adattivo, >0=fisso
input int Inp_HurstPeriod = 0; // 0=auto, >0=fisso
input int Inp_ADXPeriod = 0; // 0=auto, >0=fisso
input bool Inp_UseNeural = false; // true = neural orchestrator, false = softmax
input string Inp_NNModelFile = ""; // modello NN (vuoto = auto Nome_Symbol_TF.dat)
input bool Inp_TrainMode = false; // colleziona campioni e allena NN in backtest
input int Inp_TrainEpochs = 100; // epoche di training
input double Inp_TrainLR = 0.001; // learning rate Adam
input int Inp_NNHidden = 6; // neuroni hidden layer
input double Inp_RiskFraction = 0.20; // frazione di capitale da rischiare per trade
input bool Inp_UseReversalClose = true; // true=chiudi posizioni su segnale opposto, false=long+short coesistono
Orchestrator *orchestrator;
CTrade *trade;
CPositionInfo posInfo;
string sym;
int dig;
datetime lastBarTime = 0;
int lastBarTotal = 0;
int bufferBars = 0;
int magicNumber;
// Hash semplice per magic number
int StringHash(string s) {
int h = 0;
int len = StringLen(s);
for(int i=0; i<len; i++) {
h = (h * 31 + (int)StringGetCharacter(s, i)) % 999999;
}
return h;
}
int OnInit() {
int nAgents = 6; // Hurst, ADX, MA, Momentum, Consensus, Hunter
// Buffer bars auto: HurstMaxPeriod * 10
int hurstMaxP = (Inp_HurstPeriod > 0) ? Inp_HurstPeriod : 200;
bufferBars = (Inp_BufferBars > 0) ? Inp_BufferBars : MathMax(300, hurstMaxP * 10);
// Peso minimo auto: 1/agentCount * 0.1
double wMinAuto = 1.0 / nAgents * 0.1;
double wMin = (Inp_WMin > 0.0) ? Inp_WMin : wMinAuto;
sym = (Inp_Symbol == "") ? _Symbol : Inp_Symbol;
magicNumber = StringHash(sym + (string)Inp_TF + "MAT") % 999999;
if(magicNumber < 10000) magicNumber += 10000;
// weightAlpha: 1/(N+10) per N agenti
orchestrator = new Orchestrator(Inp_MinZ, wMin, 1.0 / (nAgents + 10),
Inp_UseNeural, Inp_NNModelFile,
Inp_TrainMode, Inp_TrainEpochs,
Inp_TrainLR, Inp_NNHidden,
Inp_RiskFraction);
trade = new CTrade();
dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
trade.SetExpertMagicNumber(magicNumber);
orchestrator.AddAgent(new RegimeDetector("Hurst", 1.0, Inp_HurstPeriod));
orchestrator.AddAgent(new RegimeADX("ADX", 1.0, Inp_ADXPeriod));
orchestrator.AddAgent(new RegimeConsensus("Consensus", 1.0));
orchestrator.AddAgent(new MAAgent("MA", 1.0, 8, 40));
orchestrator.AddAgent(new MomentumAgent("Momentum", 1.0, 6, 40));
orchestrator.AddAgent(new PatternHunter("Hunter", 1.0));
orchestrator.InitAgents(sym, Inp_TF);
orchestrator.LoadState(sym, Inp_TF);
// Carica o inizializza modello NN
if(Inp_UseNeural || Inp_TrainMode) {
if(!orchestrator.LoadNNModel()) {
if(Inp_UseNeural) {
Print(" NN model not found, initializing fresh network...");
}
orchestrator.InitNeuralNet(8, Inp_NNHidden, 3);
}
}
string modeStr = Inp_UseNeural ? "NEURAL" : "SOFTMAX";
Print("MultiAgentTest v7 avviato: ", sym, " ", EnumToString(Inp_TF), " [", modeStr, "]");
Print("Agenti: ", orchestrator.TotalAgents(), " (Hurst, ADX, MA, Momentum, Consensus, Hunter)");
Print("Min |z|: ", Inp_MinZ, " | Buffer: ", bufferBars, " barre | Neural: ", modeStr);
// Warm-up statistiche: passa i dati storici per inizializzare le EWMA
MarketData warmup(sym, Inp_TF, bufferBars);
if(warmup.Fetch()) {
Print("Warm-up statistiche... (", warmup.count, " barre)");
// Processa ogni barra storica una volta, dalla più vecchia alla più recente
for(int bar = warmup.count - 1; bar >= 1; bar--) {
MarketData single(sym, Inp_TF, 1);
single.open[0] = warmup.open[bar];
single.high[0] = warmup.high[bar];
single.low[0] = warmup.low[bar];
single.close[0] = warmup.close[bar];
single.volume[0] = warmup.volume[bar];
single.time[0] = warmup.time[bar];
single.count = 1;
orchestrator.Analyze(single);
}
// Ultima barra (current) processata una volta
orchestrator.Analyze(warmup);
Print("Hurst: H=", StringFormat("%.3f", SHARED_regimeH),
" ADX: ", StringFormat("%.1f", SHARED_adxRaw),
" Consensus: ", StringFormat("%+.2f", SHARED_regimeConsensus),
" pattern: ", SHARED_patternName);
}
if(Inp_UseNeural) {
if(orchestrator.IsNeuralReady()) {
Print(" Neural: READY — ", orchestrator.NeuralInfo());
} else {
Print(" Neural: INITIALIZED (untrained — will fallback to softmax)");
}
}
if(Inp_TrainMode) Print(" TrainMode: ON (collecting samples)");
Print(orchestrator.TotalAgents(), " agenti pronti.");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) {
CloseAllPositions();
Print("=== MultiAgentTest: Final Report ===");
Print(" Reason: ", reasonToStr(reason));
Print(" Symbol: ", sym, " | TF: ", EnumToString(Inp_TF));
Print(" Mode: ", Inp_UseNeural ? "NEURAL" : "SOFTMAX",
" | TrainMode: ", Inp_TrainMode ? "ON" : "OFF");
Print(" Trades: ", orchestrator.TradeCount(),
" | Win rate: ", orchestrator.TradeCount() > 0
? StringFormat("%.1f%%", 100.0 * orchestrator.WinCount() / orchestrator.TradeCount())
: "N/A");
PrintAgentStats();
// Neural: training e salvataggio
bool nnTrained = false;
if(Inp_TrainMode && orchestrator.TradeCount() >= 3) {
Print("=== Neural Network Training (fine backtest) ===");
Print(" Trade count: ", orchestrator.TradeCount());
double loss = orchestrator.TrainNN();
if(loss >= 0) {
nnTrained = true;
Print(" Training complete: loss=", StringFormat("%.6f", loss));
PrintNeuralStats();
} else {
Print(" Training skipped: insufficient samples (need >=3)");
}
}
// Agent learning summary
orchestrator.PrintAgentLearningSummary();
orchestrator.SaveAgentLearningCsv();
// Analisi completa: tutti i parametri derivati, soglie, periodi, stats
orchestrator.SaveAnalysisCSV(sym, Inp_TF);
// Save state file (include NN weights inline se addestrata)
orchestrator.SaveState(sym, Inp_TF);
// Salva anche NN standalone (utile per debug/backup)
if(nnTrained || orchestrator.IsNeuralReady())
orchestrator.SaveNNModel();
orchestrator.ReleaseAgents();
delete orchestrator;
delete trade;
Print("=== MultiAgentTest terminato ===");
}
string reasonToStr(int r) {
switch(r) {
case REASON_PROGRAM: return "Program";
case REASON_REMOVE: return "Remove";
case REASON_CHARTCLOSE: return "Chart Close";
case REASON_PARAMETERS: return "Parameters Changed";
case REASON_RECOMPILE: return "Recompiled";
case REASON_ACCOUNT: return "Account Changed";
case REASON_TEMPLATE: return "Template";
case REASON_INITFAILED: return "Init Failed";
case REASON_CLOSE: return "Terminal Close";
default: return "Unknown (" + (string)r + ")";
}
}
void PrintAgentStats() {
Print(" Hurst H: ", StringFormat("%.3f", SHARED_regimeH),
" | ADX: ", StringFormat("%.1f", SHARED_adxRaw),
" | Consensus: ", StringFormat("%+.2f", SHARED_regimeConsensus),
" | Agreement: ", StringFormat("%.2f", SHARED_regimeAgreement),
" | Pattern: ", SHARED_patternName);
}
void PrintNeuralStats() {
Print(" " + orchestrator.NeuralInfo());
// Feature importanza approssimata: media |W1| per input
if(orchestrator.IsNeuralReady()) {
string lines[8];
// Non possiamo accedere direttamente ai pesi
Print(" (vedi CSV per loss history e training samples)");
}
}
int CountPositions() {
int count = 0;
for(int i=PositionsTotal()-1; i>=0; i--) {
if(posInfo.SelectByIndex(i)) {
if(posInfo.Symbol() == sym && posInfo.Magic() == magicNumber)
count++;
}
}
return count;
}
void CloseAllPositions() {
for(int i=PositionsTotal()-1; i>=0; i--) {
if(posInfo.SelectByIndex(i)) {
if(posInfo.Symbol() == sym && posInfo.Magic() == magicNumber)
trade.PositionClose(posInfo.Ticket());
}
}
}
void OnTick() {
if(!IsNewBar()) return;
MarketData data(sym, Inp_TF, bufferBars);
if(!data.Fetch()) return;
double z = orchestrator.Analyze(data);
FinalSignal fs = orchestrator.GetFinalSignal(Inp_MinZ);
double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ();
if(fs.IsActionable(minZ)) {
ManagePositions(data, fs);
}
LogSignal(data, fs);
if(CountPositions() == 0 && orchestrator.HasOpenTrade()) {
orchestrator.ResetTradeState();
}
}
bool IsNewBar() {
int barsTotal = Bars(sym, Inp_TF);
datetime timeArr[];
CopyTime(sym, Inp_TF, 0, 1, timeArr);
if(ArraySize(timeArr) < 1) return false;
datetime barTime = timeArr[0];
if(lastBarTime == 0) {
lastBarTime = barTime;
lastBarTotal = barsTotal;
return false;
}
if(barTime != lastBarTime || barsTotal != lastBarTotal) {
lastBarTime = barTime;
lastBarTotal = barsTotal;
return true;
}
return false;
}
void ManagePositions(const MarketData &data, const FinalSignal &fs) {
double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ();
// 1. Update MAE/MFE per tutti gli aperti (traccia high/low intra-barra)
orchestrator.UpdateOpenTrades(data.High(0), data.Low(0));
orchestrator.TrailStops();
// 2. Reversal close (opzionale): chiudi posizioni con segnale opposto
// Se disabilitato, long e short coesistono — massimizza profitto multi-direzionale
if(Inp_UseReversalClose) {
int closeTickets[];
orchestrator.GetTradesToClose(closeTickets, fs.zScore, minZ);
for(int c = 0; c < ArraySize(closeTickets); c++) {
int tkt = closeTickets[c];
if(PositionSelectByTicket(tkt)) {
bool isBuy = orchestrator.IsBuyTrade(tkt);
double closePrice = isBuy ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK);
orchestrator.OnTradeClose(tkt, closePrice);
trade.PositionClose(tkt);
}
}
}
// 3. Aggiorna trailing stop sul terminale (tester supporta PositionModify)
for(int i=PositionsTotal()-1; i>=0; i--) {
if(posInfo.SelectByIndex(i)) {
if(posInfo.Symbol() != sym || posInfo.Magic() != magicNumber) continue;
int tkt = (int)posInfo.Ticket();
double newSL = orchestrator.GetTradeSL(tkt);
if(newSL <= 0) continue;
double curSL = posInfo.StopLoss();
bool isBuy = posInfo.PositionType() == POSITION_TYPE_BUY;
bool shouldUpdate = isBuy ? (newSL > curSL + _Point) : (newSL < curSL - _Point);
if(shouldUpdate) {
double curTP = posInfo.TakeProfit();
trade.PositionModify(tkt, newSL, curTP);
}
}
}
// 4. Apri nuova posizione OGNI BARRA con segnale actionable (aggressivo)
if(MathAbs(fs.zScore) > minZ) {
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
double price = (fs.direction == 1) ? ask : bid;
// ATR period: scala con N agenti e buffer disponibile
int atrPeriod = MathMax(orchestrator.TotalAgents() + 1, MathMin(bufferBars / 15, 50));
double atr = data.ATR(atrPeriod);
if(atr <= 0) return;
// SL adattivo via MAE (trailing stop gestisce l'uscita, no TP fisso)
double slWidth = (Inp_SLRiskATR > 0) ? Inp_SLRiskATR : orchestrator.AdaptiveSLWidth();
double sl = (fs.direction == 1) ? price - atr * slWidth
: price + atr * slWidth;
sl = NormalizeDouble(sl, dig);
double lot = (Inp_Lot > 0) ? Inp_Lot : orchestrator.AdaptiveBaseLot() * MathAbs(fs.zScore);
double lotStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
double lotMin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double lotMax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
lot = MathRound(lot / lotStep) * lotStep;
lot = MathMax(lotMin, MathMin(lotMax, lot));
// Solo il margine libero limita i trade
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
double marginReq = lot * SymbolInfoDouble(sym, SYMBOL_MARGIN_REQUIRED);
if(marginReq >= freeMargin && freeMargin > 0) {
Print("Margine insufficiente: lot=", lot, " free=", freeMargin, " req=", marginReq);
return; // Skip — solo il margine blocca i trade
}
int ticket = 0;
if(fs.direction == 1)
ticket = trade.Buy(lot, sym, ask, sl, 0); // TP=0: trailing stop
else
ticket = trade.Sell(lot, sym, bid, sl, 0);
if(ticket > 0) {
orchestrator.OnTradeOpen(ticket, price, atr);
}
}
}
void LogSignal(const MarketData &data, const FinalSignal &fs) {
string timeStr = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES);
string dir = "NONE";
if(fs.direction == 1) dir = "BUY";
if(fs.direction == -1) dir = "SELL";
string mode = Inp_UseNeural && orchestrator.IsNeuralReady() ? "NN" : "SM";
string log = timeStr + " [" + mode + "] " + dir;
log += " | z=" + StringFormat("%+.3f", fs.zScore);
log += " | conf=" + StringFormat("%.0f%%", fs.confidence*100);
log += " | agree=" + (string)fs.agreeingCount + "/" + (string)fs.totalAgents;
static int printCounter = 0;
if(printCounter % 5 == 0) {
orchestrator.PrintAgentStatus();
if(Inp_UseNeural)
Print(" ", orchestrator.NeuralInfo());
}
printCounter++;
if(MathAbs(fs.zScore) > Inp_MinZ) {
Print(log);
Print(" Agents: ", fs.contributingAgents);
}
}
// Rileva chiusure (SL/TP) fatte dal broker — multi-trade
void OnTrade() {
static int onTradeCloses = 0;
static int saveInterval = 0;
if(saveInterval == 0) saveInterval = MathMax(orchestrator.TotalAgents(), orchestrator.TotalAgents() * 5);
if(!orchestrator.HasOpenTrade()) return;
int maxSlots = orchestrator.MaxTradeSlots();
for(int t = 0; t < maxSlots; t++) {
int tkt = orchestrator.GetTrackedTicket(t);
if(tkt < 0) break; // nessun altro trade attivo
bool found = false;
for(int p = PositionsTotal()-1; p >= 0; p--) {
if(posInfo.SelectByIndex(p)) {
if((int)posInfo.Ticket() == tkt) { found = true; break; }
}
}
if(!found) {
// Trade chiuso da SL/TP (non da noi in ManagePositions)
bool isBuy = orchestrator.IsBuyTrade(tkt);
double closePrice = isBuy ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK);
orchestrator.OnTradeClose(tkt, closePrice);
onTradeCloses++;
Print("SL/TP chiuso trade #", tkt, " (totale SL/TP: ", onTradeCloses, ")");
if(onTradeCloses % saveInterval == 0)
orchestrator.SaveState(sym, Inp_TF);
}
}
}
double OnTester() {
double sharpe = orchestrator.TradeSharpe();
int trades = orchestrator.TradeCount();
Print("OnTester: ", trades, " trade, Sharpe=", StringFormat("%.3f", sharpe));
return sharpe;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.