MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes
This commit is contained in:
@@ -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 ¶m, 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 ¶m, 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 + α × (x−mean)²) (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.
Reference in New Issue
Block a user