commit 5c00f17121b7180444c8d0f7eda2e221b4d78bdf Author: pietro_giacobazzi Date: Sat Jun 13 13:58:38 2026 +0200 MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes diff --git a/DOCUMENTAZIONE_AGENTI.md b/DOCUMENTAZIONE_AGENTI.md new file mode 100644 index 0000000..11ea674 --- /dev/null +++ b/DOCUMENTAZIONE_AGENTI.md @@ -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. \ No newline at end of file diff --git a/MQL5/Experts/MultiAgentTest/Agents/AgentBase.mqh b/MQL5/Experts/MultiAgentTest/Agents/AgentBase.mqh new file mode 100644 index 0000000..7ed9a47 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/AgentBase.mqh @@ -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 diff --git a/MQL5/Experts/MultiAgentTest/Agents/MAAgent.mqh b/MQL5/Experts/MultiAgentTest/Agents/MAAgent.mqh new file mode 100644 index 0000000..5f9a18b --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/MAAgent.mqh @@ -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 diff --git a/MQL5/Experts/MultiAgentTest/Agents/MomentumAgent.mqh b/MQL5/Experts/MultiAgentTest/Agents/MomentumAgent.mqh new file mode 100644 index 0000000..2909d96 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/MomentumAgent.mqh @@ -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 diff --git a/MQL5/Experts/MultiAgentTest/Agents/PatternHunter.mqh b/MQL5/Experts/MultiAgentTest/Agents/PatternHunter.mqh new file mode 100644 index 0000000..36641b8 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/PatternHunter.mqh @@ -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 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 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 diff --git a/MQL5/Experts/MultiAgentTest/Agents/RegimeConsensus.mqh b/MQL5/Experts/MultiAgentTest/Agents/RegimeConsensus.mqh new file mode 100644 index 0000000..b5b5e94 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/RegimeConsensus.mqh @@ -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 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 diff --git a/MQL5/Experts/MultiAgentTest/Agents/RegimeDetector.mqh b/MQL5/Experts/MultiAgentTest/Agents/RegimeDetector.mqh new file mode 100644 index 0000000..a40a6df --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Agents/RegimeDetector.mqh @@ -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= targetWindows) break; + + double sumF2 = 0; + int validWin = 0; + + for(int j=0; j 1.0 - hSe || MathAbs(H - 0.5) < hSe) + H = ComputeRS(close, len); + return MathMax(hSe, MathMin(1.0 - hSe, H)); + } + + double ComputeRS(const double &close[], int len) { + targetWindows = MathMax(3, MathMin(10, len / 60)); + int minN = MathMax(3, targetWindows); + if(len < targetWindows * minN * 2) return 0.5; + + int maxN = MathMax(minN * 2, len / (targetWindows / 2)); + if(maxN < minN * 2) return 0.5; + + double returns[]; + int nRet = LogReturns(close, len, returns); + if(nRet < maxN) return 0.5; + + // Varianza di riferimento per soglia data-scaled + double retVarRef = 0; + for(int i=0; i= targetWindows) break; + + double sumRS = 0; + int validSub = 0; + + for(int j=0; j epsVarRS) ? MathSqrt(var) : 0; + if(std < MathSqrt(epsVarRS)) continue; + + double cumDev[]; + ArrayResize(cumDev, n); + cumDev[0] = returns[base] - mean; + for(int k=1; k cumDev[maxIdx]) maxIdx = k; + if(cumDev[k] < cumDev[minIdx]) minIdx = k; + } + double R = cumDev[maxIdx] - cumDev[minIdx]; + sumRS += R / std; + validSub++; + } + + if(validSub < 1) continue; + double avgRS = sumRS / validSub; + logRS[pts] = MathLog(avgRS); + logN[pts] = MathLog(n); + pts++; + } + + if(pts < 3) return 0.5; + + double sumX=0, sumY=0, sumXY=0, sumX2=0; + for(int i=0; i 0) { + hurstPeriod = userPeriod; + } else { + int cycle = PeriodCalculator::DominantCycle(data.close, data.count, 20, 100); + // Periodo: max(2x ciclo, minWindows * targetWindows) + int minForWindows = targetWindows * MathMax(3, targetWindows); + int newP = MathMax(cycle * 2, minForWindows); + newP = MathMax(20, MathMin(200, newP)); + if(hurstPeriod <= 0) hurstPeriod = newP; + else { + double alpha = 1.0 / (1.0 + warmup * 0.1); + double minAlpha = 1.0 / MathMax(2.0, (double)MathMax(1, hurstPeriod)); + alpha = MathMax(minAlpha, alpha); // solo floor, niente max clamp + hurstPeriod = (int)MathRound(alpha * newP + (1.0 - alpha) * hurstPeriod); + } + if(hurstPeriod < minPeriod) hurstPeriod = minPeriod; + } + + double H = ComputeHurst(data.close, MathMin(hurstPeriod, data.count)); + + signalStats.Update(H); + double zRaw = signalStats.ZScore(H); + + // EWMA con alpha che scala con il numero di osservazioni + double alpha = 1.0 / (1.0 + signalStats.Count() * 0.1); + double minAlpha = 1.0 / MathMax(2.0, (double)MathMax(1, hurstPeriod)); + alpha = MathMax(minAlpha, alpha); // solo floor, niente max clamp + prevZ = (1.0 - alpha) * prevZ + alpha * zRaw; + double calibrated = CalibrateZ(prevZ); + lastZScore = MathTanh(calibrated); + lastRawSignal = H; + + SHARED_regimeH = H; + + return lastZScore; + } + + void Interact(IAgent *&allAgents[], int count) override {} + + void Learn(double predictedZ, double actualReturnZ) override {} + + void Save(int fh) const override { + IAgent::Save(fh); + FileWriteDouble(fh, prevZ); + } + void Load(int fh) override { + IAgent::Load(fh); + prevZ = FileReadDouble(fh); + warmup = signalStats.Count(); // ripristina warmup dal conteggio statistiche + } + + void Reset() override { + IAgent::Reset(); + prevZ = 0; + warmup = 0; + } + + string SignalInfo() const override { + return name + " z=" + StringFormat("%+.3f", lastZScore) + + " H=" + StringFormat("%.3f", SHARED_regimeH) + + " p=" + (string)hurstPeriod + + " " + signalStats.ToString(); + } +}; +#endif diff --git a/MQL5/Experts/MultiAgentTest/Core/MarketData.mqh b/MQL5/Experts/MultiAgentTest/Core/MarketData.mqh new file mode 100644 index 0000000..5c4c0d9 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/Core/MarketData.mqh @@ -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= 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