diff --git a/ForexBot2_XAUUSD_M5.mq5 b/ForexBot2_XAUUSD_M5.mq5 new file mode 100644 index 0000000..f2c3461 --- /dev/null +++ b/ForexBot2_XAUUSD_M5.mq5 @@ -0,0 +1,1960 @@ +//+------------------------------------------------------------------+ +//| ForexBot2_XAUUSD_M5.mq5 | +//| 1:1 port of ForexBot2 Python signal engine for XAU/USD M5 | +//| 9 indicators, regime-adaptive weights, MTF agreement, | +//| diminishing penalty cap, quality grading, auto-trade (max 1 pos) | +//+------------------------------------------------------------------+ +#property copyright "ForexBot2" +#property version "1.00" +#property strict + +#include + +//+------------------------------------------------------------------+ +//| Input Parameters | +//+------------------------------------------------------------------+ +input double InpLotSize = 0.01; // Lot Size +input int InpMagicNumber = 202505; // Magic Number +input int InpMaxSlippage = 30; // Max Slippage (points) +input string InpMinQuality = "C"; // Minimum quality for entry (A+,A,B,C) +input double InpBETriggerR = 0.15; // Breakeven trigger (fraction of risk) +input double InpBEBufferR = 0.05; // Breakeven buffer (fraction of risk) +input double InpSLAtrMult = 2.0; // SL ATR multiplier (A+/A setup) +input double InpTPAtrMult = 1.0; // TP ATR multiplier base +input double InpGenericSLFloor = 2.5; // SL ATR floor (B/C generic setup) +input double InpMinRR = 0.5; // Minimum risk-reward ratio +input double InpMinATR = 1.0; // Minimum ATR to trade (price units) +input bool InpWeekendFilter = true; // Enable weekend filter +input string InpBlockedHours = "0,1,2"; // Blocked UTC hours (comma-separated) +input string InpAllowedDays = "0,1,2,3,4,6"; // Allowed days 0=Mon..6=Sun +input bool InpDebugMode = false; // Print detailed signal info + +//+------------------------------------------------------------------+ +//| Enums | +//+------------------------------------------------------------------+ +enum ENUM_SIGNAL_STRENGTH +{ + SS_STRONG_SELL = -2, + SS_SELL = -1, + SS_NEUTRAL = 0, + SS_BUY = 1, + SS_STRONG_BUY = 2 +}; + +enum ENUM_MARKET_REGIME +{ + MR_TRENDING_UP, + MR_TRENDING_DOWN, + MR_TRENDING, + MR_RANGING, + MR_TRANSITIONING +}; + +enum ENUM_SIGNAL_QUALITY +{ + SQ_APLUS, + SQ_A, + SQ_B, + SQ_C, + SQ_D, + SQ_F +}; + +//+------------------------------------------------------------------+ +//| Structs | +//+------------------------------------------------------------------+ +struct IndicatorSignal +{ + string name; + double value; + ENUM_SIGNAL_STRENGTH signal; + double confidence; + double weight; +}; + +struct SwingPoint +{ + int idx; + double val; +}; + +struct TechnicalAnalysis +{ + IndicatorSignal indicators[12]; + int indicatorCount; + ENUM_SIGNAL_STRENGTH compositeSignal; + double compositeScore; + double confidence; + ENUM_MARKET_REGIME marketRegime; + string volatilityRegime; + double ema_fast; + double ema_medium; + double ema_slow; + double ema_trend; + double bb_upper; + double bb_lower; + double bb_mid; + double kc_upper; + double kc_lower; + double kc_mid; + double atr_val; + double atr_pct; + double swing_high[3]; + double swing_low[3]; + int swing_high_count; + int swing_low_count; + double rsi_series[]; + double macd_histogram[]; + double adx_slope; + bool kc_squeeze; + bool kc_squeeze_released; +}; + +struct SignalReport +{ + ENUM_SIGNAL_STRENGTH primarySignal; + double primaryScore; + double primaryConfidence; + bool mtfAgreement; + ENUM_SIGNAL_STRENGTH mtfSignals[3]; + double mtfScores[3]; + int mtfCount; + ENUM_SIGNAL_STRENGTH finalSignal; + double finalScore; + double finalConfidence; + ENUM_SIGNAL_QUALITY signalQuality; + ENUM_MARKET_REGIME marketRegime; + string volatilityRegime; + double currentPrice; + int exhaustionCount; +}; + +struct TradeSetup +{ + string direction; + double entry; + double sl; + double tp1; + double tp2; + double rr; +}; + +struct PenaltyItem +{ + string name; + double amount; +}; + +//+------------------------------------------------------------------+ +//| Global Variables | +//+------------------------------------------------------------------+ + +// Indicator handles — M5 +int h_ema9_m5, h_ema21_m5, h_ema50_m5, h_ema200_m5; +int h_rsi_m5, h_macd_m5, h_bb_m5, h_adx_m5, h_atr14_m5; +int h_kc_ema20_m5, h_kc_atr10_m5; + +// Indicator handles — M15 +int h_ema9_m15, h_ema21_m15, h_ema50_m15, h_ema200_m15; +int h_rsi_m15, h_macd_m15, h_bb_m15, h_adx_m15, h_atr14_m15; +int h_kc_ema20_m15, h_kc_atr10_m15; + +// Indicator handles — H1 +int h_ema9_h1, h_ema21_h1, h_ema50_h1, h_ema200_h1; +int h_rsi_h1, h_macd_h1, h_bb_h1, h_adx_h1, h_atr14_h1; +int h_kc_ema20_h1, h_kc_atr10_h1; + +// New bar detection +datetime g_lastBarTime = 0; + +// Position tracking for breakeven +double g_posEntryPrice = 0; +double g_posRisk = 0; +bool g_beTriggered = false; + +// Parsed filter arrays +int g_blockedHours[]; +int g_blockedHoursCount = 0; +int g_allowedDays[]; +int g_allowedDaysCount = 0; + +// Trade helper +CTrade g_trade; + +//+------------------------------------------------------------------+ +//| Regime weight tables | +//+------------------------------------------------------------------+ +// Index: 0=ema_trend, 1=rsi, 2=macd, 3=bollinger, 4=keltner, +// 5=roc, 6=candlestick, 7=divergence, 8=adx +double g_trendingWeights[9] = {2.0, 1.0, 2.0, 0.5, 1.5, 1.5, 0.8, 2.0, 0.8}; +double g_rangingWeights[9] = {0.3, 1.5, 1.5, 1.5, 2.0, 0.5, 1.0, 2.5, 0.5}; +double g_transitioningWeights[9] = {1.5, 1.0, 2.5, 1.0, 2.5, 2.0, 1.0, 2.0, 1.0}; + +//+------------------------------------------------------------------+ +//| Helper: map indicator name to regime weight index | +//+------------------------------------------------------------------+ +int IndicatorNameToIndex(const string &name) +{ + if(name == "EMA Trend") return 0; + if(name == "RSI") return 1; + if(name == "MACD") return 2; + if(name == "Bollinger Bands") return 3; + if(name == "Keltner") return 4; + if(name == "ROC") return 5; + if(name == "Candlestick") return 6; + if(name == "Divergence") return 7; + if(name == "ADX") return 8; + return -1; +} + +//+------------------------------------------------------------------+ +//| Helper: parse comma-separated ints | +//+------------------------------------------------------------------+ +int ParseIntList(const string &csv, int &arr[]) +{ + string parts[]; + int count = StringSplit(csv, ',', parts); + ArrayResize(arr, count); + for(int i = 0; i < count; i++) + arr[i] = (int)StringToInteger(StringTrimLeft(StringTrimRight(parts[i]))); + return count; +} + +//+------------------------------------------------------------------+ +//| Helper: check if int is in array | +//+------------------------------------------------------------------+ +bool IntInArray(int val, const int &arr[], int count) +{ + for(int i = 0; i < count; i++) + if(arr[i] == val) return true; + return false; +} + +//+------------------------------------------------------------------+ +//| Helper: quality enum to string | +//+------------------------------------------------------------------+ +string QualityToString(ENUM_SIGNAL_QUALITY q) +{ + switch(q) + { + case SQ_APLUS: return "A+"; + case SQ_A: return "A"; + case SQ_B: return "B"; + case SQ_C: return "C"; + case SQ_D: return "D"; + default: return "F"; + } +} + +//+------------------------------------------------------------------+ +//| Helper: string to quality enum | +//+------------------------------------------------------------------+ +ENUM_SIGNAL_QUALITY StringToQuality(const string &s) +{ + if(s == "A+") return SQ_APLUS; + if(s == "A") return SQ_A; + if(s == "B") return SQ_B; + if(s == "C") return SQ_C; + if(s == "D") return SQ_D; + return SQ_F; +} + +//+------------------------------------------------------------------+ +//| Helper: is quality sufficient for entry | +//+------------------------------------------------------------------+ +bool IsQualitySufficient(ENUM_SIGNAL_QUALITY q) +{ + ENUM_SIGNAL_QUALITY minQ = StringToQuality(InpMinQuality); + return (q <= minQ); // enum order: APLUS=0 < A=1 < B=2 < C=3 < D=4 < F=5 +} + +//+------------------------------------------------------------------+ +//| Helper: create indicator handle with validation | +//+------------------------------------------------------------------+ +int CreateHandle(int handle, const string &desc) +{ + if(handle == INVALID_HANDLE) + { + PrintFormat("Failed to create %s handle: %d", desc, GetLastError()); + return INVALID_HANDLE; + } + return handle; +} + +//+------------------------------------------------------------------+ +//| Create all indicator handles for a timeframe | +//+------------------------------------------------------------------+ +bool CreateHandlesForTF(ENUM_TIMEFRAMES tf, + int &h_ema9, int &h_ema21, int &h_ema50, int &h_ema200, + int &h_rsi, int &h_macd, int &h_bb, int &h_adx, int &h_atr14, + int &h_kc_ema20, int &h_kc_atr10) +{ + string tfStr = EnumToString(tf); + h_ema9 = CreateHandle(iMA(_Symbol, tf, 9, 0, MODE_EMA, PRICE_CLOSE), "EMA9 " + tfStr); + h_ema21 = CreateHandle(iMA(_Symbol, tf, 21, 0, MODE_EMA, PRICE_CLOSE), "EMA21 " + tfStr); + h_ema50 = CreateHandle(iMA(_Symbol, tf, 50, 0, MODE_EMA, PRICE_CLOSE), "EMA50 " + tfStr); + h_ema200 = CreateHandle(iMA(_Symbol, tf, 200,0, MODE_EMA, PRICE_CLOSE), "EMA200 "+ tfStr); + h_rsi = CreateHandle(iRSI(_Symbol, tf, 14, PRICE_CLOSE), "RSI " + tfStr); + h_macd = CreateHandle(iMACD(_Symbol, tf, 12, 26, 9, PRICE_CLOSE), "MACD " + tfStr); + h_bb = CreateHandle(iBands(_Symbol, tf, 20, 0, 2.0, PRICE_CLOSE), "BB " + tfStr); + h_adx = CreateHandle(iADX(_Symbol, tf, 14), "ADX " + tfStr); + h_atr14 = CreateHandle(iATR(_Symbol, tf, 14), "ATR14 " + tfStr); + h_kc_ema20= CreateHandle(iMA(_Symbol, tf, 20, 0, MODE_EMA, PRICE_CLOSE), "KC_EMA20 " + tfStr); + h_kc_atr10= CreateHandle(iATR(_Symbol, tf, 10), "KC_ATR10 " + tfStr); + + return (h_ema9 != INVALID_HANDLE && h_ema21 != INVALID_HANDLE && + h_ema50 != INVALID_HANDLE && h_ema200 != INVALID_HANDLE && + h_rsi != INVALID_HANDLE && h_macd != INVALID_HANDLE && + h_bb != INVALID_HANDLE && h_adx != INVALID_HANDLE && + h_atr14 != INVALID_HANDLE && h_kc_ema20 != INVALID_HANDLE && + h_kc_atr10 != INVALID_HANDLE); +} + +//+------------------------------------------------------------------+ +//| OnInit | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(!CreateHandlesForTF(PERIOD_M5, h_ema9_m5, h_ema21_m5, h_ema50_m5, h_ema200_m5, + h_rsi_m5, h_macd_m5, h_bb_m5, h_adx_m5, h_atr14_m5, + h_kc_ema20_m5, h_kc_atr10_m5)) + return INIT_FAILED; + + if(!CreateHandlesForTF(PERIOD_M15, h_ema9_m15, h_ema21_m15, h_ema50_m15, h_ema200_m15, + h_rsi_m15, h_macd_m15, h_bb_m15, h_adx_m15, h_atr14_m15, + h_kc_ema20_m15, h_kc_atr10_m15)) + return INIT_FAILED; + + if(!CreateHandlesForTF(PERIOD_H1, h_ema9_h1, h_ema21_h1, h_ema50_h1, h_ema200_h1, + h_rsi_h1, h_macd_h1, h_bb_h1, h_adx_h1, h_atr14_h1, + h_kc_ema20_h1, h_kc_atr10_h1)) + return INIT_FAILED; + + g_blockedHoursCount = ParseIntList(InpBlockedHours, g_blockedHours); + g_allowedDaysCount = ParseIntList(InpAllowedDays, g_allowedDays); + + g_trade.SetExpertMagicNumber(InpMagicNumber); + g_trade.SetDeviationInPoints(InpMaxSlippage); + g_trade.SetTypeFilling(ORDER_FILLING_IOC); + + Print("ForexBot2 XAUUSD M5 EA initialized — 30 indicator handles created"); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| OnDeinit | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + // M5 + IndicatorRelease(h_ema9_m5); IndicatorRelease(h_ema21_m5); + IndicatorRelease(h_ema50_m5); IndicatorRelease(h_ema200_m5); + IndicatorRelease(h_rsi_m5); IndicatorRelease(h_macd_m5); + IndicatorRelease(h_bb_m5); IndicatorRelease(h_adx_m5); + IndicatorRelease(h_atr14_m5); IndicatorRelease(h_kc_ema20_m5); + IndicatorRelease(h_kc_atr10_m5); + // M15 + IndicatorRelease(h_ema9_m15); IndicatorRelease(h_ema21_m15); + IndicatorRelease(h_ema50_m15); IndicatorRelease(h_ema200_m15); + IndicatorRelease(h_rsi_m15); IndicatorRelease(h_macd_m15); + IndicatorRelease(h_bb_m15); IndicatorRelease(h_adx_m15); + IndicatorRelease(h_atr14_m15); IndicatorRelease(h_kc_ema20_m15); + IndicatorRelease(h_kc_atr10_m15); + // H1 + IndicatorRelease(h_ema9_h1); IndicatorRelease(h_ema21_h1); + IndicatorRelease(h_ema50_h1); IndicatorRelease(h_ema200_h1); + IndicatorRelease(h_rsi_h1); IndicatorRelease(h_macd_h1); + IndicatorRelease(h_bb_h1); IndicatorRelease(h_adx_h1); + IndicatorRelease(h_atr14_h1); IndicatorRelease(h_kc_ema20_h1); + IndicatorRelease(h_kc_atr10_h1); +} + +//+------------------------------------------------------------------+ +//| Add indicator to TechnicalAnalysis | +//+------------------------------------------------------------------+ +void AddIndicator(TechnicalAnalysis &ta, const string &name, double value, + ENUM_SIGNAL_STRENGTH sig, double conf, double wt) +{ + if(ta.indicatorCount >= 12) return; + int idx = ta.indicatorCount; + ta.indicators[idx].name = name; + ta.indicators[idx].value = value; + ta.indicators[idx].signal = sig; + ta.indicators[idx].confidence = conf; + ta.indicators[idx].weight = wt; + ta.indicatorCount++; +} + +//+------------------------------------------------------------------+ +//| Get indicator by name from TechnicalAnalysis | +//+------------------------------------------------------------------+ +bool GetIndicator(const TechnicalAnalysis &ta, const string &name, IndicatorSignal &out) +{ + for(int i = 0; i < ta.indicatorCount; i++) + { + if(ta.indicators[i].name == name) + { + out = ta.indicators[i]; + return true; + } + } + return false; +} + +//══════════════════════════════════════════════════════════════════════ +// INDICATOR COMPUTATIONS +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| 1. ATR — volatility measurement | +//+------------------------------------------------------------------+ +void ComputeATR(int h_atr, ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double atr_buf[50]; + if(CopyBuffer(h_atr, 0, 1, 50, atr_buf) < 50) return; + ArraySetAsSeries(atr_buf, true); + + ta.atr_val = atr_buf[0]; + double close[]; + if(CopyClose(_Symbol, tf, 1, 1, close) < 1) return; + ta.atr_pct = (close[0] != 0) ? (ta.atr_val / close[0]) * 100.0 : 0; + + int countBelow = 0; + for(int i = 1; i < 50; i++) + if(atr_buf[i] < atr_buf[0]) countBelow++; + double percentile = (countBelow / 49.0) * 100.0; + + if(percentile > 80) ta.volatilityRegime = "extreme"; + else if(percentile > 60) ta.volatilityRegime = "high"; + else if(percentile > 30) ta.volatilityRegime = "normal"; + else ta.volatilityRegime = "low"; + + AddIndicator(ta, "ATR", ta.atr_val, SS_NEUTRAL, 50, 0.0); +} + +//+------------------------------------------------------------------+ +//| 2. EMA 4-EMA trend alignment (9/21/50/200) | +//+------------------------------------------------------------------+ +void ComputeEMA4(int h_e9, int h_e21, int h_e50, int h_e200, + ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double e9[6], e21[6], e50[6], e200[6]; + if(CopyBuffer(h_e9, 0, 1, 6, e9) < 6) return; + if(CopyBuffer(h_e21, 0, 1, 6, e21) < 6) return; + if(CopyBuffer(h_e50, 0, 1, 6, e50) < 6) return; + if(CopyBuffer(h_e200,0, 1, 6, e200)< 6) return; + ArraySetAsSeries(e9, true); + ArraySetAsSeries(e21, true); + ArraySetAsSeries(e50, true); + ArraySetAsSeries(e200, true); + + double close[]; + if(CopyClose(_Symbol, tf, 1, 1, close) < 1) return; + double price = close[0]; + double f = e9[0], m = e21[0], s = e50[0], t = e200[0]; + + ta.ema_fast = f; + ta.ema_medium = m; + ta.ema_slow = s; + ta.ema_trend = t; + + int alignment = 0; + if(price > f) alignment++; else if(price < f) alignment--; + if(f > m) alignment++; else if(f < m) alignment--; + if(m > s) alignment++; else if(m < s) alignment--; + if(s > t) alignment++; else if(s < t) alignment--; + + double normalized = alignment / 4.0; + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(normalized > 0.6) { sig = SS_STRONG_BUY; conf = MathMin(90, 60 + normalized * 30); } + else if(normalized > 0.2) { sig = SS_BUY; conf = 55 + normalized * 20; } + else if(normalized < -0.6) { sig = SS_STRONG_SELL; conf = MathMin(90, 60 + MathAbs(normalized) * 30); } + else if(normalized < -0.2) { sig = SS_SELL; conf = 55 + MathAbs(normalized) * 20; } + else { sig = SS_NEUTRAL; conf = 40; } + + // Golden/death cross (EMA9/EMA21) + if(e9[1] <= e21[1] && f > m) conf = MathMin(conf + 10, 95); + else if(e9[1] >= e21[1] && f < m) conf = MathMin(conf + 10, 95); + + // Ribbon compression/expansion + if(ta.atr_val > 0) + { + double ema_vals[3] = {f, m, s}; + double mx = MathMax(MathMax(f, m), s); + double mn = MathMin(MathMin(f, m), s); + double ribbonW = (mx - mn) / ta.atr_val; + + if(ribbonW < 1.0) + { + // compressed — check if expanding from previous + double prev_mx = MathMax(MathMax(e9[4], e21[4]), e50[4]); + double prev_mn = MathMin(MathMin(e9[4], e21[4]), e50[4]); + double prevW = (prev_mx - prev_mn) / ta.atr_val; + if(prevW < 1.0 && ribbonW >= 1.0) + conf = MathMin(conf + 10, 95); + } + else + { + double prev_mx = MathMax(MathMax(e9[4], e21[4]), e50[4]); + double prev_mn = MathMin(MathMin(e9[4], e21[4]), e50[4]); + double prevW = (prev_mx - prev_mn) / ta.atr_val; + if(prevW < 1.0) + conf = MathMin(conf + 10, 95); + } + } + + AddIndicator(ta, "EMA Trend", normalized, sig, conf, 1.0); +} + +//+------------------------------------------------------------------+ +//| 3. RSI | +//+------------------------------------------------------------------+ +void ComputeRSI(int h_rsi, ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + int lookback = 20; + double rsi_buf[]; + ArrayResize(rsi_buf, lookback); + if(CopyBuffer(h_rsi, 0, 1, lookback, rsi_buf) < lookback) return; + ArraySetAsSeries(rsi_buf, true); + + double rsi_val = rsi_buf[0]; + + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(rsi_val >= 70.0) { sig = SS_SELL; conf = MathMin(80, 50 + (rsi_val - 70)); } + else if(rsi_val <= 30.0) { sig = SS_BUY; conf = MathMin(80, 50 + (30 - rsi_val)); } + else if(rsi_val >= 60.0) { sig = SS_BUY; conf = 45; } + else if(rsi_val <= 40.0) { sig = SS_SELL; conf = 45; } + else { sig = SS_NEUTRAL; conf = 30; } + + AddIndicator(ta, "RSI", rsi_val, sig, conf, 1.5); + + // Store series for divergence (oldest-first for swing detection) + ArrayResize(ta.rsi_series, lookback); + for(int i = 0; i < lookback; i++) + ta.rsi_series[i] = rsi_buf[lookback - 1 - i]; // reverse to oldest-first +} + +//+------------------------------------------------------------------+ +//| 4. MACD (using iMACD built-in) | +//+------------------------------------------------------------------+ +void ComputeMACD(int h_macd, ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double macd_line[30], signal_line[30]; + if(CopyBuffer(h_macd, 0, 1, 30, macd_line) < 30) return; + if(CopyBuffer(h_macd, 1, 1, 30, signal_line) < 30) return; + ArraySetAsSeries(macd_line, true); + ArraySetAsSeries(signal_line, true); + + // Compute histogram + double hist[30]; + for(int i = 0; i < 30; i++) + hist[i] = macd_line[i] - signal_line[i]; + + // Store histogram for divergence + Keltner (oldest-first) + ArrayResize(ta.macd_histogram, 30); + for(int i = 0; i < 30; i++) + ta.macd_histogram[i] = hist[30 - 1 - i]; // oldest first + + double hist_now = hist[0]; + double hist_prev = hist[1]; + + bool crossed_up = (hist_prev <= 0 && hist_now > 0); + bool crossed_down= (hist_prev >= 0 && hist_now < 0); + bool accel_up = (hist_now > 0 && hist_now > hist_prev && hist_prev > 0); + bool accel_down = (hist_now < 0 && hist_now < hist_prev && hist_prev < 0); + bool fading_up = (hist_now > 0 && hist_now < hist_prev); + bool fading_down = (hist_now < 0 && hist_now > hist_prev); + + // Normalize histogram vs 20-bar average + double hist_abs_sum = 0; + for(int i = 0; i < 20; i++) hist_abs_sum += MathAbs(hist[i]); + double hist_abs_mean = hist_abs_sum / 20.0; + double hist_pct = (hist_abs_mean > 0) ? MathAbs(hist_now) / hist_abs_mean : 1.0; + + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(crossed_up || accel_up) + { + sig = accel_up ? SS_STRONG_BUY : SS_BUY; + conf = MathMin(80, 55 + hist_pct * 10); + } + else if(crossed_down || accel_down) + { + sig = accel_down ? SS_STRONG_SELL : SS_SELL; + conf = MathMin(80, 55 + hist_pct * 10); + } + else if(fading_up || fading_down) + { + sig = SS_NEUTRAL; + conf = 35; + } + else + { + sig = SS_NEUTRAL; + conf = 30; + } + + AddIndicator(ta, "MACD", hist_now, sig, conf, 1.5); +} + +//+------------------------------------------------------------------+ +//| 5. Bollinger Bands | +//+------------------------------------------------------------------+ +void ComputeBollinger(int h_bb, ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double bb_mid_buf[20], bb_upper_buf[20], bb_lower_buf[20]; + if(CopyBuffer(h_bb, 0, 1, 20, bb_mid_buf) < 20) return; + if(CopyBuffer(h_bb, 1, 1, 20, bb_upper_buf) < 20) return; + if(CopyBuffer(h_bb, 2, 1, 20, bb_lower_buf) < 20) return; + ArraySetAsSeries(bb_mid_buf, true); + ArraySetAsSeries(bb_upper_buf, true); + ArraySetAsSeries(bb_lower_buf, true); + + double close[]; + if(CopyClose(_Symbol, tf, 1, 1, close) < 1) return; + double price = close[0]; + + double upper = bb_upper_buf[0]; + double lower = bb_lower_buf[0]; + double mid = bb_mid_buf[0]; + + ta.bb_upper = upper; + ta.bb_lower = lower; + ta.bb_mid = mid; + + double bb_range = upper - lower; + double bb_position = (bb_range != 0) ? (price - lower) / bb_range : 0.5; + double band_width = (mid != 0) ? bb_range / mid : 0; + + // Squeeze detection + bool is_squeeze = false; + double avg_width = 0; + for(int i = 0; i < 20; i++) + { + double w = (bb_mid_buf[i] != 0) ? (bb_upper_buf[i] - bb_lower_buf[i]) / bb_mid_buf[i] : 0; + avg_width += w; + } + avg_width /= 20.0; + if(avg_width > 0) + is_squeeze = (band_width < avg_width * 0.75); + + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(bb_position > 1.0) { sig = SS_SELL; conf = 65; } + else if(bb_position < 0.0) { sig = SS_BUY; conf = 65; } + else if(bb_position > 0.8) { sig = SS_SELL; conf = 45; } + else if(bb_position < 0.2) { sig = SS_BUY; conf = 45; } + else { sig = SS_NEUTRAL; conf = 30; } + + if(is_squeeze) conf = MathMin(conf + 10, 85); + + AddIndicator(ta, "Bollinger Bands", bb_position, sig, conf, 1.5); +} + +//+------------------------------------------------------------------+ +//| 6. ADX | +//+------------------------------------------------------------------+ +void ComputeADX(int h_adx, ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double adx_main[6], plus_di[6], minus_di[6]; + if(CopyBuffer(h_adx, 0, 1, 6, adx_main) < 6) return; + if(CopyBuffer(h_adx, 1, 1, 6, plus_di) < 6) return; + if(CopyBuffer(h_adx, 2, 1, 6, minus_di) < 6) return; + ArraySetAsSeries(adx_main, true); + ArraySetAsSeries(plus_di, true); + ArraySetAsSeries(minus_di, true); + + double adx_val = adx_main[0]; + double plus_di_v = plus_di[0]; + double minus_di_v = minus_di[0]; + + // ADX slope (lookback=5: current - 5 bars ago) + ta.adx_slope = adx_main[0] - adx_main[5]; + + double conf; + if(adx_val >= 50.0) conf = 80; + else if(adx_val >= 25.0) conf = 65; + else conf = 40; + + ENUM_SIGNAL_STRENGTH sig; + if(plus_di_v > minus_di_v) + sig = (adx_val >= 25.0) ? SS_BUY : SS_NEUTRAL; + else + sig = (adx_val >= 25.0) ? SS_SELL : SS_NEUTRAL; + + // Slope adjustments + if(adx_val < 25.0 && ta.adx_slope > 3.0) + conf = MathMin(conf + 15, 85); + else if(adx_val >= 25.0 && ta.adx_slope > 0) + conf = MathMin(conf + 5, 85); + else if(adx_val >= 25.0 && ta.adx_slope < -3.0) + conf = MathMax(conf - 10, 30); + + AddIndicator(ta, "ADX", adx_val, sig, conf, 0.5); +} + +//+------------------------------------------------------------------+ +//| 7. ROC (Rate of Change) | +//+------------------------------------------------------------------+ +void ComputeROC(ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double close_buf[15]; + if(CopyClose(_Symbol, tf, 1, 15, close_buf) < 15) return; + ArraySetAsSeries(close_buf, true); + // close_buf[0]=latest closed bar, [10]=10 bars ago, [11]=11 bars ago + + if(close_buf[10] == 0 || close_buf[11] == 0) return; + + double roc_val = ((close_buf[0] - close_buf[10]) / close_buf[10]) * 100.0; + double roc_prev = ((close_buf[1] - close_buf[11]) / close_buf[11]) * 100.0; + + bool roc_rising = (roc_val > roc_prev); + double roc_abs = MathAbs(roc_val); + + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(roc_val > 0 && roc_rising) + { + if(roc_abs > 2.0) { sig = SS_STRONG_BUY; conf = MathMin(80, 60 + roc_abs * 5); } + else { sig = SS_BUY; conf = MathMin(70, 50 + roc_abs * 10); } + } + else if(roc_val < 0 && !roc_rising) + { + if(roc_abs > 2.0) { sig = SS_STRONG_SELL; conf = MathMin(80, 60 + roc_abs * 5); } + else { sig = SS_SELL; conf = MathMin(70, 50 + roc_abs * 10); } + } + else if(roc_prev <= 0 && roc_val > 0) + { + sig = SS_BUY; conf = 65; + } + else if(roc_prev >= 0 && roc_val < 0) + { + sig = SS_SELL; conf = 65; + } + else + { + sig = SS_NEUTRAL; conf = 30; + } + + AddIndicator(ta, "ROC", roc_val, sig, conf, 1.5); +} + +//+------------------------------------------------------------------+ +//| 8. Keltner Channels + TTM Squeeze | +//+------------------------------------------------------------------+ +void ComputeKeltner(int h_kc_ema, int h_kc_atr, int h_bb, + ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + double kc_ema_buf[2], kc_atr_buf[2]; + if(CopyBuffer(h_kc_ema, 0, 1, 2, kc_ema_buf) < 2) return; + if(CopyBuffer(h_kc_atr, 0, 1, 2, kc_atr_buf) < 2) return; + ArraySetAsSeries(kc_ema_buf, true); + ArraySetAsSeries(kc_atr_buf, true); + + double kc_upper_now = kc_ema_buf[0] + 1.5 * kc_atr_buf[0]; + double kc_lower_now = kc_ema_buf[0] - 1.5 * kc_atr_buf[0]; + double kc_upper_prev= kc_ema_buf[1] + 1.5 * kc_atr_buf[1]; + double kc_lower_prev= kc_ema_buf[1] - 1.5 * kc_atr_buf[1]; + + ta.kc_upper = kc_upper_now; + ta.kc_lower = kc_lower_now; + ta.kc_mid = kc_ema_buf[0]; + + // Get BB values for squeeze + double bb_upper_buf[2], bb_lower_buf[2]; + if(CopyBuffer(h_bb, 1, 1, 2, bb_upper_buf) < 2) return; + if(CopyBuffer(h_bb, 2, 1, 2, bb_lower_buf) < 2) return; + ArraySetAsSeries(bb_upper_buf, true); + ArraySetAsSeries(bb_lower_buf, true); + + bool is_squeeze = (bb_lower_buf[0] > kc_lower_now && bb_upper_buf[0] < kc_upper_now); + bool was_squeeze = (bb_lower_buf[1] > kc_lower_prev && bb_upper_buf[1] < kc_upper_prev); + bool squeeze_released = (was_squeeze && !is_squeeze); + + ta.kc_squeeze = is_squeeze; + ta.kc_squeeze_released = squeeze_released; + + // MACD histogram direction for squeeze release + int macd_dir = 0; + int hist_size = ArraySize(ta.macd_histogram); + if(hist_size > 0) + { + double last_hist = ta.macd_histogram[hist_size - 1]; // most recent (oldest-first array) + macd_dir = (last_hist > 0) ? 1 : ((last_hist < 0) ? -1 : 0); + } + + double close[]; + if(CopyClose(_Symbol, tf, 1, 1, close) < 1) return; + double price = close[0]; + + ENUM_SIGNAL_STRENGTH sig; + double conf; + + if(squeeze_released) + { + if(macd_dir > 0) { sig = SS_STRONG_BUY; conf = 80; } + else if(macd_dir < 0) { sig = SS_STRONG_SELL; conf = 80; } + else { sig = SS_NEUTRAL; conf = 80; } + } + else if(is_squeeze) + { + sig = SS_NEUTRAL; conf = 60; + } + else if(price > kc_upper_now) + { + sig = SS_BUY; conf = 65; + } + else if(price < kc_lower_now) + { + sig = SS_SELL; conf = 65; + } + else + { + sig = SS_NEUTRAL; conf = 30; + } + + AddIndicator(ta, "Keltner", price - kc_ema_buf[0], sig, conf, 1.5); +} + +//+------------------------------------------------------------------+ +//| 9. Swing S/R detection | +//+------------------------------------------------------------------+ +int FindSwingPoints(const double &arr[], int arrSize, SwingPoint &points[], + int maxPoints, bool isLow, int window = 3) +{ + int count = 0; + ArrayResize(points, maxPoints); + for(int i = window; i < arrSize - window && count < maxPoints; i++) + { + bool isSwing = true; + for(int j = 1; j <= window; j++) + { + if(isLow) + { + if(arr[i] > arr[i - j] || arr[i] > arr[i + j]) { isSwing = false; break; } + } + else + { + if(arr[i] < arr[i - j] || arr[i] < arr[i + j]) { isSwing = false; break; } + } + } + if(isSwing) + { + points[count].idx = i; + points[count].val = arr[i]; + count++; + } + } + return count; +} + +void ComputeSwingSR(ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + int lookback = 50; + double high_buf[50], low_buf[50]; + if(CopyHigh(_Symbol, tf, 1, lookback, high_buf) < lookback) return; + if(CopyLow(_Symbol, tf, 1, lookback, low_buf) < lookback) return; + // Reverse to oldest-first + double high_arr[50], low_arr[50]; + for(int i = 0; i < lookback; i++) + { + high_arr[i] = high_buf[lookback - 1 - i]; + low_arr[i] = low_buf[lookback - 1 - i]; + } + + double close[]; + if(CopyClose(_Symbol, tf, 1, 1, close) < 1) return; + double price = close[0]; + + SwingPoint sh[20], sl_pts[20]; + int sh_count = FindSwingPoints(high_arr, lookback, sh, 20, false, 3); + int sl_count = FindSwingPoints(low_arr, lookback, sl_pts, 20, true, 3); + + // Highs above price (sorted ascending) — nearest first + double highs_above[]; + int ha_count = 0; + for(int i = 0; i < sh_count; i++) + if(sh[i].val > price) + { + ArrayResize(highs_above, ha_count + 1); + highs_above[ha_count++] = sh[i].val; + } + if(ha_count > 1) ArraySort(highs_above); + + // Lows below price (sorted descending) — nearest first + double lows_below[]; + int lb_count = 0; + for(int i = 0; i < sl_count; i++) + if(sl_pts[i].val < price) + { + ArrayResize(lows_below, lb_count + 1); + lows_below[lb_count++] = sl_pts[i].val; + } + // Sort ascending then we'll read from end + if(lb_count > 1) ArraySort(lows_below); + + ta.swing_high_count = MathMin(ha_count, 3); + for(int i = 0; i < ta.swing_high_count; i++) + ta.swing_high[i] = highs_above[i]; + + ta.swing_low_count = MathMin(lb_count, 3); + for(int i = 0; i < ta.swing_low_count; i++) + ta.swing_low[i] = lows_below[lb_count - 1 - i]; // descending: nearest first + + // Fallback if no levels found + if(ta.swing_high_count == 0 && sh_count > 0) + { + // Use highest swing high below price + double best = 0; + for(int i = 0; i < sh_count; i++) + if(sh[i].val <= price && sh[i].val > best) best = sh[i].val; + if(best > 0) { ta.swing_high[0] = best; ta.swing_high_count = 1; } + } + if(ta.swing_low_count == 0 && sl_count > 0) + { + double best = 999999; + for(int i = 0; i < sl_count; i++) + if(sl_pts[i].val >= price && sl_pts[i].val < best) best = sl_pts[i].val; + if(best < 999999) { ta.swing_low[0] = best; ta.swing_low_count = 1; } + } +} + +//+------------------------------------------------------------------+ +//| 10. Divergence detection | +//+------------------------------------------------------------------+ +void ComputeDivergence(ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + int lookback = 14; + double close_buf[]; + if(CopyClose(_Symbol, tf, 1, lookback, close_buf) < lookback) return; + // Reverse to oldest-first + double price_window[14]; + for(int i = 0; i < lookback; i++) + price_window[i] = close_buf[lookback - 1 - i]; + + int rsi_size = ArraySize(ta.rsi_series); + int hist_size= ArraySize(ta.macd_histogram); + + bool bullish_found = false, bearish_found = false; + + // Check RSI divergence + if(rsi_size >= lookback) + { + double rsi_window[14]; + int rsi_start = rsi_size - lookback; + for(int i = 0; i < lookback; i++) + rsi_window[i] = ta.rsi_series[rsi_start + i]; + + SwingPoint p_lows[10], r_lows[10], p_highs[10], r_highs[10]; + int pl = FindSwingPoints(price_window, lookback, p_lows, 10, true, 3); + int rl = FindSwingPoints(rsi_window, lookback, r_lows, 10, true, 3); + if(pl >= 2 && rl >= 2) + if(p_lows[pl-1].val < p_lows[pl-2].val && r_lows[rl-1].val > r_lows[rl-2].val) + bullish_found = true; + + int ph = FindSwingPoints(price_window, lookback, p_highs, 10, false, 3); + int rh = FindSwingPoints(rsi_window, lookback, r_highs, 10, false, 3); + if(ph >= 2 && rh >= 2) + if(p_highs[ph-1].val > p_highs[ph-2].val && r_highs[rh-1].val < r_highs[rh-2].val) + bearish_found = true; + } + + // Check MACD divergence + if(hist_size >= lookback) + { + double macd_window[14]; + int macd_start = hist_size - lookback; + for(int i = 0; i < lookback; i++) + macd_window[i] = ta.macd_histogram[macd_start + i]; + + SwingPoint p_lows2[10], m_lows[10], p_highs2[10], m_highs[10]; + int pl2 = FindSwingPoints(price_window, lookback, p_lows2, 10, true, 3); + int ml = FindSwingPoints(macd_window, lookback, m_lows, 10, true, 3); + if(pl2 >= 2 && ml >= 2) + if(p_lows2[pl2-1].val < p_lows2[pl2-2].val && m_lows[ml-1].val > m_lows[ml-2].val) + bullish_found = true; + + int ph2 = FindSwingPoints(price_window, lookback, p_highs2, 10, false, 3); + int mh = FindSwingPoints(macd_window, lookback, m_highs, 10, false, 3); + if(ph2 >= 2 && mh >= 2) + if(p_highs2[ph2-1].val > p_highs2[ph2-2].val && m_highs[mh-1].val < m_highs[mh-2].val) + bearish_found = true; + } + + if(bullish_found || bearish_found) + { + ENUM_SIGNAL_STRENGTH sig; + if(bullish_found && !bearish_found) sig = SS_BUY; + else if(bearish_found && !bullish_found) sig = SS_SELL; + else sig = SS_NEUTRAL; + + AddIndicator(ta, "Divergence", (double)(bullish_found ? 1 : 0) + (bearish_found ? 1 : 0), + sig, 70, 1.5); + } +} + +//+------------------------------------------------------------------+ +//| 11. Candlestick patterns | +//+------------------------------------------------------------------+ +void ComputeCandlestick(ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + MqlRates rates[3]; + if(CopyRates(_Symbol, tf, 1, 3, rates) < 3) return; + ArraySetAsSeries(rates, true); + + double o = rates[0].open, h = rates[0].high, l = rates[0].low, c = rates[0].close; + double prev_o = rates[1].open, prev_c = rates[1].close; + double body = MathAbs(c - o); + double upper_wick = h - MathMax(o, c); + double lower_wick = MathMin(o, c) - l; + double full_range = h - l; + + if(full_range == 0) return; + double body_ratio = body / full_range; + + int bullish_count = 0, bearish_count = 0; + + // Doji + if(body_ratio < 0.1) { /* indecision, no directional count */ } + + // Hammer / Hanging Man + if(lower_wick > 2 * body && upper_wick < body * 0.5 && body_ratio > 0.1) + { + if(c > o) bullish_count++; + else bearish_count++; + } + + // Shooting Star / Inverted Hammer + if(upper_wick > 2 * body && lower_wick < body * 0.5 && body_ratio > 0.1) + { + if(c < o) bearish_count++; + else bullish_count++; + } + + // Engulfing + if(prev_c < prev_o && c > o && c > prev_o && o < prev_c) + bullish_count++; + if(prev_c > prev_o && c < o && c < prev_o && o > prev_c) + bearish_count++; + + // Marubozu + if(body_ratio > 0.85) + { + if(c > o) bullish_count++; + else bearish_count++; + } + + if(bullish_count == 0 && bearish_count == 0) return; + + ENUM_SIGNAL_STRENGTH sig; + if(bullish_count > bearish_count) sig = SS_BUY; + else if(bearish_count > bullish_count) sig = SS_SELL; + else sig = SS_NEUTRAL; + + double conf = 50; + + // S/R proximity boost + if(ta.atr_val > 0) + { + double proximity = ta.atr_val * 0.5; + bool near_support = false, near_resistance = false; + + for(int i = 0; i < ta.swing_low_count; i++) + if(MathAbs(c - ta.swing_low[i]) <= proximity) { near_support = true; break; } + for(int i = 0; i < ta.swing_high_count; i++) + if(MathAbs(c - ta.swing_high[i]) <= proximity) { near_resistance = true; break; } + + if(bullish_count > bearish_count && near_support) conf += 20; + if(bearish_count > bullish_count && near_resistance) conf += 20; + } + + AddIndicator(ta, "Candlestick", (double)(bullish_count - bearish_count), sig, conf, 1.0); +} + +//══════════════════════════════════════════════════════════════════════ +// REGIME CLASSIFICATION & COMPOSITE +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Classify market regime | +//+------------------------------------------------------------------+ +void ClassifyRegime(TechnicalAnalysis &ta) +{ + IndicatorSignal adx_ind, ema_ind; + bool hasADX = GetIndicator(ta, "ADX", adx_ind); + bool hasEMA = GetIndicator(ta, "EMA Trend", ema_ind); + + if(hasADX && adx_ind.value >= 25.0) + { + if(hasEMA && (ema_ind.signal == SS_BUY || ema_ind.signal == SS_STRONG_BUY)) + ta.marketRegime = MR_TRENDING_UP; + else if(hasEMA && (ema_ind.signal == SS_SELL || ema_ind.signal == SS_STRONG_SELL)) + ta.marketRegime = MR_TRENDING_DOWN; + else + ta.marketRegime = MR_TRENDING; + } + else if(hasADX && ta.adx_slope > 3.0) + { + ta.marketRegime = MR_TRANSITIONING; + } + else + { + ta.marketRegime = MR_RANGING; + } +} + +//+------------------------------------------------------------------+ +//| Compute composite score with regime-adaptive weights | +//+------------------------------------------------------------------+ +void ComputeComposite(TechnicalAnalysis &ta) +{ + double total_score = 0, total_weight = 0, total_confidence = 0; + + for(int i = 0; i < ta.indicatorCount; i++) + { + if(ta.indicators[i].weight == 0) continue; + + int wIdx = IndicatorNameToIndex(ta.indicators[i].name); + double regime_mult = 1.0; + if(wIdx >= 0) + { + switch(ta.marketRegime) + { + case MR_TRENDING_UP: + case MR_TRENDING_DOWN: + case MR_TRENDING: + regime_mult = g_trendingWeights[wIdx]; break; + case MR_RANGING: + regime_mult = g_rangingWeights[wIdx]; break; + case MR_TRANSITIONING: + regime_mult = g_transitioningWeights[wIdx]; break; + } + } + + double weight = ta.indicators[i].weight * regime_mult; + double sig_val = (double)ta.indicators[i].signal; + double conf = ta.indicators[i].confidence; + + double weighted_score = sig_val * weight * (conf / 100.0); + + total_score += weighted_score; + total_weight += weight; + total_confidence += conf * weight; + } + + if(total_weight > 0) + { + ta.compositeScore = MathMax(-100, MathMin(100, (total_score / total_weight) * 50.0)); + ta.confidence = total_confidence / total_weight; + } + else + { + ta.compositeScore = 0; + ta.confidence = 0; + } + + if(ta.compositeScore >= 40) ta.compositeSignal = SS_STRONG_BUY; + else if(ta.compositeScore >= 15) ta.compositeSignal = SS_BUY; + else if(ta.compositeScore <= -40) ta.compositeSignal = SS_STRONG_SELL; + else if(ta.compositeScore <= -15) ta.compositeSignal = SS_SELL; + else ta.compositeSignal = SS_NEUTRAL; +} + +//══════════════════════════════════════════════════════════════════════ +// FULL INDICATOR PIPELINE +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Compute all indicators for a timeframe | +//+------------------------------------------------------------------+ +void ComputeAllIndicators(ENUM_TIMEFRAMES tf, TechnicalAnalysis &ta) +{ + ta.indicatorCount = 0; + ta.swing_high_count = 0; + ta.swing_low_count = 0; + ta.kc_squeeze = false; + ta.kc_squeeze_released = false; + ta.adx_slope = 0; + ta.atr_val = 0; + ta.volatilityRegime = "normal"; + ArrayFree(ta.rsi_series); + ArrayFree(ta.macd_histogram); + + int hE9, hE21, hE50, hE200, hRSI, hMACD, hBB, hADX, hATR14, hKCema, hKCatr; + + if(tf == PERIOD_M5) + { + hE9=h_ema9_m5; hE21=h_ema21_m5; hE50=h_ema50_m5; hE200=h_ema200_m5; + hRSI=h_rsi_m5; hMACD=h_macd_m5; hBB=h_bb_m5; hADX=h_adx_m5; hATR14=h_atr14_m5; + hKCema=h_kc_ema20_m5; hKCatr=h_kc_atr10_m5; + } + else if(tf == PERIOD_M15) + { + hE9=h_ema9_m15; hE21=h_ema21_m15; hE50=h_ema50_m15; hE200=h_ema200_m15; + hRSI=h_rsi_m15; hMACD=h_macd_m15; hBB=h_bb_m15; hADX=h_adx_m15; hATR14=h_atr14_m15; + hKCema=h_kc_ema20_m15; hKCatr=h_kc_atr10_m15; + } + else // H1 + { + hE9=h_ema9_h1; hE21=h_ema21_h1; hE50=h_ema50_h1; hE200=h_ema200_h1; + hRSI=h_rsi_h1; hMACD=h_macd_h1; hBB=h_bb_h1; hADX=h_adx_h1; hATR14=h_atr14_h1; + hKCema=h_kc_ema20_h1; hKCatr=h_kc_atr10_h1; + } + + // Order matters for dependencies + ComputeATR(hATR14, tf, ta); // 1. ATR first + ComputeEMA4(hE9, hE21, hE50, hE200, tf, ta); // 2. EMA (needs ATR) + ComputeRSI(hRSI, tf, ta); // 3. RSI (stores series) + ComputeMACD(hMACD, tf, ta); // 4. MACD (stores histogram) + ComputeBollinger(hBB, tf, ta); // 5. BB (stores levels) + ComputeADX(hADX, tf, ta); // 6. ADX (stores slope) + ComputeROC(tf, ta); // 7. ROC + ComputeKeltner(hKCema, hKCatr, hBB, tf, ta); // 8. Keltner (needs BB+MACD) + ComputeSwingSR(tf, ta); // 9. Swing S/R + ComputeDivergence(tf, ta); // 10. Divergence (needs RSI+MACD series) + ComputeCandlestick(tf, ta); // 11. Candlestick (needs ATR+swing) +} + +//══════════════════════════════════════════════════════════════════════ +// MTF AGREEMENT & FINAL SIGNAL +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Check MTF agreement | +//+------------------------------------------------------------------+ +bool CheckMTFAgreement(const SignalReport &report) +{ + int bullish = 0, bearish = 0; + for(int i = 0; i < report.mtfCount; i++) + { + if(report.mtfSignals[i] == SS_BUY || report.mtfSignals[i] == SS_STRONG_BUY) + bullish++; + if(report.mtfSignals[i] == SS_SELL || report.mtfSignals[i] == SS_STRONG_SELL) + bearish++; + } + return (bullish >= 2 || bearish >= 2); +} + +//+------------------------------------------------------------------+ +//| Add penalty to array | +//+------------------------------------------------------------------+ +void AddPenalty(PenaltyItem &penalties[], int &count, const string &name, double amount) +{ + ArrayResize(penalties, count + 1); + penalties[count].name = name; + penalties[count].amount = amount; + count++; +} + +//+------------------------------------------------------------------+ +//| Sort penalties descending by amount | +//+------------------------------------------------------------------+ +void SortPenaltiesDesc(PenaltyItem &penalties[], int count) +{ + for(int i = 0; i < count - 1; i++) + for(int j = i + 1; j < count; j++) + if(penalties[j].amount > penalties[i].amount) + { + PenaltyItem tmp = penalties[i]; + penalties[i] = penalties[j]; + penalties[j] = tmp; + } +} + +//+------------------------------------------------------------------+ +//| Compute final signal with bonuses and penalties | +//+------------------------------------------------------------------+ +void ComputeFinalSignal(SignalReport &report, const TechnicalAnalysis &primaryTA) +{ + double score = report.primaryScore; + double confidence = report.primaryConfidence; + + // ── Bonuses ── + if(report.mtfAgreement) + { + confidence += 10; + double mtf_avg = 0; + for(int i = 0; i < report.mtfCount; i++) mtf_avg += report.mtfScores[i]; + mtf_avg /= report.mtfCount; + score = score * 0.6 + mtf_avg * 0.4; + } + + // RSI pullback zone bonus in trend + IndicatorSignal rsi_ind; + bool hasRSI = false; + for(int i = 0; i < primaryTA.indicatorCount; i++) + if(primaryTA.indicators[i].name == "RSI") { rsi_ind = primaryTA.indicators[i]; hasRSI = true; break; } + + if(hasRSI) + { + if((report.marketRegime == MR_TRENDING_UP || report.marketRegime == MR_TRENDING) && + score > 0 && rsi_ind.value >= 38 && rsi_ind.value <= 55) + confidence += 5; + if(report.marketRegime == MR_TRENDING_DOWN && + score < 0 && rsi_ind.value >= 45 && rsi_ind.value <= 62) + confidence += 5; + } + + // BB pullback zone bonus + IndicatorSignal bb_ind; + bool hasBB = false; + for(int i = 0; i < primaryTA.indicatorCount; i++) + if(primaryTA.indicators[i].name == "Bollinger Bands") { bb_ind = primaryTA.indicators[i]; hasBB = true; break; } + + if(hasBB) + { + if((report.marketRegime == MR_TRENDING_UP || report.marketRegime == MR_TRENDING) && + score > 0 && bb_ind.value < 0.4) + confidence += 5; + if(report.marketRegime == MR_TRENDING_DOWN && + score < 0 && bb_ind.value > 0.6) + confidence += 5; + } + + // ── Penalties ── + PenaltyItem penalties[]; + int penaltyCount = 0; + + if(!report.mtfAgreement) + AddPenalty(penalties, penaltyCount, "mtf_disagreement", 15); + + bool is_counter = (report.marketRegime == MR_TRENDING_UP && score < 0) || + (report.marketRegime == MR_TRENDING_DOWN && score > 0); + if(is_counter) + AddPenalty(penalties, penaltyCount, "counter_trend", 15); + + if(report.volatilityRegime == "extreme") + AddPenalty(penalties, penaltyCount, "extreme_vol", 10); + + // EMA200 proximity + if(primaryTA.ema_trend > 0 && report.currentPrice > 0) + { + double ema_dist_pct = MathAbs(report.currentPrice - primaryTA.ema_trend) / primaryTA.ema_trend * 100.0; + if(ema_dist_pct < 0.5) + AddPenalty(penalties, penaltyCount, "ema200_near", 5); + else if(ema_dist_pct > 2.5) + AddPenalty(penalties, penaltyCount, "ema200_far", 5); + } + + // Exhaustion: price extended >3.5 ATR past swing + double atr = primaryTA.atr_val; + if(atr > 0 && score > 0 && primaryTA.swing_low_count > 0) + { + if(report.currentPrice - primaryTA.swing_low[0] > 3.5 * atr) + AddPenalty(penalties, penaltyCount, "exhaustion", 8); + } + if(atr > 0 && score < 0 && primaryTA.swing_high_count > 0) + { + if(primaryTA.swing_high[0] - report.currentPrice > 3.5 * atr) + AddPenalty(penalties, penaltyCount, "exhaustion", 8); + } + + // No pullback / weak pullback + bool isTrending = (report.marketRegime == MR_TRENDING_UP || + report.marketRegime == MR_TRENDING_DOWN || + report.marketRegime == MR_TRENDING); + if(atr > 0 && primaryTA.ema_medium > 0 && isTrending) + { + double pullback_dist = MathAbs(report.currentPrice - primaryTA.ema_medium) / atr; + if(pullback_dist > 2.5) + AddPenalty(penalties, penaltyCount, "no_pullback", 8); + else if(pullback_dist > 2.0) + AddPenalty(penalties, penaltyCount, "weak_pullback", 5); + } + + // RSI extended in trend + if(hasRSI && isTrending) + { + if((report.marketRegime == MR_TRENDING_UP || report.marketRegime == MR_TRENDING) && + score > 0 && rsi_ind.value > 65) + AddPenalty(penalties, penaltyCount, "rsi_extended", 5); + if(report.marketRegime == MR_TRENDING_DOWN && score < 0 && rsi_ind.value < 35) + AddPenalty(penalties, penaltyCount, "rsi_extended", 5); + } + + // BB overextended + if(hasBB && isTrending) + { + if((report.marketRegime == MR_TRENDING_UP || report.marketRegime == MR_TRENDING) && + score > 0 && bb_ind.value > 0.85) + AddPenalty(penalties, penaltyCount, "bb_overextended", 8); + if(report.marketRegime == MR_TRENDING_DOWN && score < 0 && bb_ind.value < 0.15) + AddPenalty(penalties, penaltyCount, "bb_overextended", 8); + } + + // ── Exhaustion block ── + int exhaustion_count = 0; + for(int i = 0; i < penaltyCount; i++) + { + if(penalties[i].name == "exhaustion" || penalties[i].name == "no_pullback" || + penalties[i].name == "rsi_extended" || penalties[i].name == "bb_overextended") + exhaustion_count++; + } + report.exhaustionCount = exhaustion_count; + + bool momentum_fading = false; + if(exhaustion_count >= 2) + { + IndicatorSignal roc_ind, macd_ind; + bool hasROC = false, hasMACD = false; + for(int i = 0; i < primaryTA.indicatorCount; i++) + { + if(primaryTA.indicators[i].name == "ROC") { roc_ind = primaryTA.indicators[i]; hasROC = true; } + if(primaryTA.indicators[i].name == "MACD") { macd_ind = primaryTA.indicators[i]; hasMACD = true; } + } + + bool roc_fading = false, macd_fading = false; + if(hasROC) + { + if(score > 0 && roc_ind.value < 0) roc_fading = true; + if(score < 0 && roc_ind.value > 0) roc_fading = true; + } + if(hasMACD) + { + if(score > 0 && (macd_ind.signal == SS_SELL || macd_ind.signal == SS_STRONG_SELL)) macd_fading = true; + if(score < 0 && (macd_ind.signal == SS_BUY || macd_ind.signal == SS_STRONG_BUY)) macd_fading = true; + } + momentum_fading = (roc_fading || macd_fading); + } + bool exhaustion_blocked = (exhaustion_count >= 2 && momentum_fading); + + // ── Apply diminishing penalties ── + SortPenaltiesDesc(penalties, penaltyCount); + double MAX_PENALTY = 30.0; + double total_penalty = 0, decay = 1.0; + for(int i = 0; i < penaltyCount; i++) + { + double applied = penalties[i].amount * decay; + if(total_penalty + applied > MAX_PENALTY) + applied = MAX_PENALTY - total_penalty; + total_penalty += applied; + decay *= 0.5; + if(total_penalty >= MAX_PENALTY) break; + } + + confidence -= total_penalty; + confidence = MathMax(0.0, MathMin(95.0, confidence)); + + if(exhaustion_blocked) confidence = 0; + + // ── Final signal thresholds ── + if(score >= 35 && confidence >= 55) report.finalSignal = SS_STRONG_BUY; + else if(score >= 25 && confidence >= 50) report.finalSignal = SS_BUY; + else if(score <= -35 && confidence >= 55) report.finalSignal = SS_STRONG_SELL; + else if(score <= -25 && confidence >= 50) report.finalSignal = SS_SELL; + else report.finalSignal = SS_NEUTRAL; + + report.finalScore = score; + report.finalConfidence = confidence; + + // ── Quality grading ── + double abs_score = MathAbs(score); + if(abs_score >= 40 && confidence >= 65 && report.mtfAgreement) + report.signalQuality = SQ_APLUS; + else if(abs_score >= 33 && confidence >= 55 && report.mtfAgreement) + report.signalQuality = SQ_A; + else if(abs_score >= 28 && confidence >= 50) + report.signalQuality = SQ_B; + else if(abs_score >= 25 && confidence >= 45) + report.signalQuality = SQ_C; + else if(abs_score >= 15) + report.signalQuality = SQ_D; + else + report.signalQuality = SQ_F; + + // ── Transitioning regime boost ── + if(report.marketRegime == MR_TRANSITIONING && + (report.signalQuality == SQ_B || report.signalQuality == SQ_C || report.signalQuality == SQ_D)) + { + if(abs_score >= 30 && confidence >= 50 && report.mtfAgreement) + report.signalQuality = SQ_A; + else if(abs_score >= 25 && confidence >= 45) + report.signalQuality = SQ_B; + } + + // ── SELL quality gate ── + bool is_sell = (report.finalSignal == SS_SELL || report.finalSignal == SS_STRONG_SELL); + if(is_sell && report.signalQuality != SQ_APLUS && report.signalQuality != SQ_A && report.signalQuality != SQ_B) + report.signalQuality = SQ_D; +} + +//══════════════════════════════════════════════════════════════════════ +// TRADE SETUP GENERATION +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| XAU ATR-based trade setup (A+/A grades) | +//+------------------------------------------------------------------+ +bool GenerateXauAtrSetup(const SignalReport &report, const TechnicalAnalysis &ta, TradeSetup &setup) +{ + double atr = ta.atr_val; + double price = report.currentPrice; + if(atr < InpMinATR) return false; + + bool is_buy; + if(report.finalSignal == SS_BUY || report.finalSignal == SS_STRONG_BUY) + is_buy = true; + else if(report.finalSignal == SS_SELL || report.finalSignal == SS_STRONG_SELL) + is_buy = false; + else + is_buy = (report.finalScore >= 0); + + double quality_scale = 0.75; + if(report.signalQuality == SQ_APLUS) quality_scale = 2.0; + else if(report.signalQuality == SQ_A) + { + quality_scale = 1.5; + if(report.exhaustionCount >= 2) quality_scale = 1.0; + } + + double tp_mult = InpTPAtrMult * quality_scale; + + if(is_buy) + { + setup.direction = "BUY"; + setup.sl = price - InpSLAtrMult * atr; + setup.tp1 = price + tp_mult * atr; + setup.tp2 = price + tp_mult * 1.5 * atr; + } + else + { + setup.direction = "SELL"; + setup.sl = price + InpSLAtrMult * atr; + setup.tp1 = price - tp_mult * atr; + setup.tp2 = price - tp_mult * 1.5 * atr; + } + setup.entry = price; + + double sl_dist = MathAbs(price - setup.sl); + double tp_dist = MathAbs(setup.tp1 - price); + setup.rr = (sl_dist > 0) ? tp_dist / sl_dist : 0; + + if(setup.rr < InpMinRR) return false; + return true; +} + +//+------------------------------------------------------------------+ +//| Generic trade setup using swing S/R (B/C grades) | +//+------------------------------------------------------------------+ +bool GenerateGenericSetup(const SignalReport &report, const TechnicalAnalysis &ta, TradeSetup &setup) +{ + double atr = ta.atr_val; + double price = report.currentPrice; + if(atr < InpMinATR) return false; + + bool is_buy; + if(report.finalSignal == SS_BUY || report.finalSignal == SS_STRONG_BUY) + is_buy = true; + else if(report.finalSignal == SS_SELL || report.finalSignal == SS_STRONG_SELL) + is_buy = false; + else + is_buy = (report.finalScore >= 0); + + double sl_atr_floor = InpGenericSLFloor; + double sl_swing_buffer = 1.0; + bool mature_move = (MathAbs(report.finalScore) > 30); + double tp_mult = mature_move ? 1.0 : 1.5; + double tp_swing_cap = mature_move ? 2.0 : 2.5; + double min_sl_price = 80.0 * 0.10; // XAU pip_value=0.10 + + if(is_buy) + { + double sl_price; + if(ta.swing_low_count > 0) + sl_price = ta.swing_low[0] - atr * sl_swing_buffer; + else + sl_price = price - atr * (1.0 + sl_swing_buffer); + + double min_sl = MathMax(atr * sl_atr_floor, min_sl_price); + if(price - sl_price < min_sl) sl_price = price - min_sl; + + double sl_distance = price - sl_price; + + double tp1; + if(ta.swing_high_count > 0 && + ta.swing_high[0] - price <= atr * tp_swing_cap && + ta.swing_high[0] - price >= sl_distance * InpMinRR) + tp1 = ta.swing_high[0]; + else + tp1 = price + MathMax(atr * tp_mult, sl_distance * InpMinRR); + + double tp2; + if(ta.swing_high_count >= 2 && ta.swing_high[1] - price <= atr * 5.0) + tp2 = ta.swing_high[1]; + else + tp2 = price + MathMax(atr * (tp_mult + 1.5), sl_distance * InpMinRR * 1.5); + + setup.direction = "BUY"; + setup.entry = price; + setup.sl = sl_price; + setup.tp1 = tp1; + setup.tp2 = tp2; + } + else + { + double sl_price; + if(ta.swing_high_count > 0) + sl_price = ta.swing_high[0] + atr * sl_swing_buffer; + else + sl_price = price + atr * (1.0 + sl_swing_buffer); + + double min_sl = MathMax(atr * sl_atr_floor, min_sl_price); + if(sl_price - price < min_sl) sl_price = price + min_sl; + + double sl_distance = sl_price - price; + + double tp1; + if(ta.swing_low_count > 0 && + price - ta.swing_low[0] <= atr * tp_swing_cap && + price - ta.swing_low[0] >= sl_distance * InpMinRR) + tp1 = ta.swing_low[0]; + else + tp1 = price - MathMax(atr * tp_mult, sl_distance * InpMinRR); + + double tp2; + if(ta.swing_low_count >= 2 && price - ta.swing_low[1] <= atr * 5.0) + tp2 = ta.swing_low[1]; + else + tp2 = price - MathMax(atr * (tp_mult + 1.5), sl_distance * InpMinRR * 1.5); + + setup.direction = "SELL"; + setup.entry = price; + setup.sl = sl_price; + setup.tp1 = tp1; + setup.tp2 = tp2; + } + + double tp1_dist = MathAbs(setup.tp1 - setup.entry); + double sl_dist = MathAbs(setup.entry - setup.sl); + setup.rr = (sl_dist > 0) ? tp1_dist / sl_dist : 0; + + if(setup.rr < InpMinRR) return false; + return true; +} + +//══════════════════════════════════════════════════════════════════════ +// ORDER MANAGEMENT +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Check if we have an open position | +//+------------------------------------------------------------------+ +bool HasOpenPosition() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(PositionSelectByTicket(ticket)) + { + if(PositionGetString(POSITION_SYMBOL) == _Symbol && + PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) + return true; + } + } + return false; +} + +//+------------------------------------------------------------------+ +//| Open a new order | +//+------------------------------------------------------------------+ +bool OpenOrder(const TradeSetup &setup) +{ + double sl = NormalizeDouble(setup.sl, _Digits); + double tp = NormalizeDouble(setup.tp1, _Digits); + + bool result = false; + if(setup.direction == "BUY") + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + result = g_trade.Buy(InpLotSize, _Symbol, ask, sl, tp, "FB2 M5"); + } + else + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + result = g_trade.Sell(InpLotSize, _Symbol, bid, sl, tp, "FB2 M5"); + } + + if(result) + { + g_posEntryPrice = (setup.direction == "BUY") ? + SymbolInfoDouble(_Symbol, SYMBOL_ASK) : + SymbolInfoDouble(_Symbol, SYMBOL_BID); + g_posRisk = MathAbs(g_posEntryPrice - setup.sl); + g_beTriggered = false; + PrintFormat("ORDER OPENED: %s at %.2f | SL=%.2f | TP=%.2f | RR=%.2f", + setup.direction, g_posEntryPrice, setup.sl, setup.tp1, setup.rr); + } + else + { + PrintFormat("ORDER FAILED: %s | Error: %d", setup.direction, GetLastError()); + } + + return result; +} + +//+------------------------------------------------------------------+ +//| Manage breakeven | +//+------------------------------------------------------------------+ +void ManageBreakeven() +{ + if(InpBETriggerR <= 0 || g_posRisk <= 0 || g_beTriggered) return; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; + + double entry = PositionGetDouble(POSITION_PRICE_OPEN); + ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + + double mfe; + if(type == POSITION_TYPE_BUY) + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + mfe = (bid - entry) / g_posRisk; + } + else + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + mfe = (entry - ask) / g_posRisk; + } + + if(mfe >= InpBETriggerR) + { + double newSL; + if(type == POSITION_TYPE_BUY) + newSL = entry + InpBEBufferR * g_posRisk; + else + newSL = entry - InpBEBufferR * g_posRisk; + + newSL = NormalizeDouble(newSL, _Digits); + double currentTP = PositionGetDouble(POSITION_TP); + + if(g_trade.PositionModify(ticket, newSL, currentTP)) + { + g_beTriggered = true; + PrintFormat("BREAKEVEN: SL moved to %.2f (trigger %.1f%% of risk)", newSL, InpBETriggerR * 100); + } + } + } +} + +//══════════════════════════════════════════════════════════════════════ +// SESSION FILTERS +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Check if forex weekend | +//+------------------------------------------------------------------+ +bool IsForexWeekend() +{ + MqlDateTime dt; + TimeGMT(dt); + // Friday after 22:00 UTC + if(dt.day_of_week == 5 && dt.hour >= 22) return true; + // All Saturday + if(dt.day_of_week == 6) return true; + // Sunday before 22:00 UTC + if(dt.day_of_week == 0 && dt.hour < 22) return true; + return false; +} + +//+------------------------------------------------------------------+ +//| Check if current hour is blocked | +//+------------------------------------------------------------------+ +bool IsBlockedHour() +{ + MqlDateTime dt; + TimeGMT(dt); + return IntInArray(dt.hour, g_blockedHours, g_blockedHoursCount); +} + +//+------------------------------------------------------------------+ +//| Check if current day is allowed | +//+------------------------------------------------------------------+ +bool IsAllowedDay() +{ + if(g_allowedDaysCount == 0) return true; + MqlDateTime dt; + TimeGMT(dt); + // Convert MQL5 day_of_week (0=Sun,1=Mon..6=Sat) to Python (0=Mon..6=Sun) + int pyDay; + if(dt.day_of_week == 0) pyDay = 6; // Sunday → 6 + else pyDay = dt.day_of_week - 1; // Mon=0, Tue=1, ..., Sat=5 + return IntInArray(pyDay, g_allowedDays, g_allowedDaysCount); +} + +//+------------------------------------------------------------------+ +//| New bar detection | +//+------------------------------------------------------------------+ +bool IsNewBar() +{ + datetime barTime = iTime(_Symbol, PERIOD_M5, 0); + if(barTime != g_lastBarTime) + { + g_lastBarTime = barTime; + return true; + } + return false; +} + +//══════════════════════════════════════════════════════════════════════ +// DEBUG OUTPUT +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| Print signal report for debugging | +//+------------------------------------------------------------------+ +void PrintSignalReport(const SignalReport &report, const TechnicalAnalysis &ta) +{ + string regime; + switch(report.marketRegime) + { + case MR_TRENDING_UP: regime = "TRENDING_UP"; break; + case MR_TRENDING_DOWN: regime = "TRENDING_DOWN"; break; + case MR_TRENDING: regime = "TRENDING"; break; + case MR_RANGING: regime = "RANGING"; break; + case MR_TRANSITIONING: regime = "TRANSITIONING"; break; + default: regime = "UNKNOWN"; + } + + string sigName; + switch(report.finalSignal) + { + case SS_STRONG_BUY: sigName = "STRONG_BUY"; break; + case SS_BUY: sigName = "BUY"; break; + case SS_NEUTRAL: sigName = "NEUTRAL"; break; + case SS_SELL: sigName = "SELL"; break; + case SS_STRONG_SELL: sigName = "STRONG_SELL"; break; + } + + PrintFormat("═══ SIGNAL REPORT ═══"); + PrintFormat("Price: %.2f | Regime: %s | Vol: %s", report.currentPrice, regime, report.volatilityRegime); + PrintFormat("Primary: score=%.1f conf=%.1f", report.primaryScore, report.primaryConfidence); + PrintFormat("MTF: %s | Scores: M5=%.1f M15=%.1f H1=%.1f", + report.mtfAgreement ? "AGREE" : "DISAGREE", + report.mtfScores[0], report.mtfScores[1], report.mtfScores[2]); + PrintFormat("Final: %s | score=%.1f | conf=%.1f | quality=%s", + sigName, report.finalScore, report.finalConfidence, QualityToString(report.signalQuality)); + + for(int i = 0; i < ta.indicatorCount; i++) + { + string indSig; + switch(ta.indicators[i].signal) + { + case SS_STRONG_BUY: indSig = "S_BUY"; break; + case SS_BUY: indSig = "BUY"; break; + case SS_NEUTRAL: indSig = "---"; break; + case SS_SELL: indSig = "SELL"; break; + case SS_STRONG_SELL: indSig = "S_SELL"; break; + } + PrintFormat(" %s: val=%.4f sig=%s conf=%.0f wt=%.1f", + ta.indicators[i].name, ta.indicators[i].value, + indSig, ta.indicators[i].confidence, ta.indicators[i].weight); + } + PrintFormat("═════════════════════"); +} + +//══════════════════════════════════════════════════════════════════════ +// MAIN TICK HANDLER +//══════════════════════════════════════════════════════════════════════ + +//+------------------------------------------------------------------+ +//| OnTick | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Breakeven management runs every tick when position exists + if(HasOpenPosition()) + { + ManageBreakeven(); + return; // max 1 order — skip signal generation + } + + // Signal generation only on new M5 bar + if(!IsNewBar()) return; + + // Session filters + if(InpWeekendFilter && IsForexWeekend()) return; + if(IsBlockedHour()) return; + if(!IsAllowedDay()) return; + + // ── 1. Primary M5 analysis ── + TechnicalAnalysis m5TA; + ComputeAllIndicators(PERIOD_M5, m5TA); + ClassifyRegime(m5TA); + ComputeComposite(m5TA); + + SignalReport report; + report.primarySignal = m5TA.compositeSignal; + report.primaryScore = m5TA.compositeScore; + report.primaryConfidence = m5TA.confidence; + report.marketRegime = m5TA.marketRegime; + report.volatilityRegime = m5TA.volatilityRegime; + report.currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + report.mtfSignals[0] = m5TA.compositeSignal; + report.mtfScores[0] = m5TA.compositeScore; + report.mtfCount = 1; + + // ── 2. M15 confirmation ── + TechnicalAnalysis m15TA; + ComputeAllIndicators(PERIOD_M15, m15TA); + ClassifyRegime(m15TA); + ComputeComposite(m15TA); + report.mtfSignals[1] = m15TA.compositeSignal; + report.mtfScores[1] = m15TA.compositeScore; + report.mtfCount = 2; + + // ── 3. H1 confirmation ── + TechnicalAnalysis h1TA; + ComputeAllIndicators(PERIOD_H1, h1TA); + ClassifyRegime(h1TA); + ComputeComposite(h1TA); + report.mtfSignals[2] = h1TA.compositeSignal; + report.mtfScores[2] = h1TA.compositeScore; + report.mtfCount = 3; + + // ── 4. MTF agreement ── + report.mtfAgreement = CheckMTFAgreement(report); + + // ── 5. Final signal ── + ComputeFinalSignal(report, m5TA); + + // ── 6. Debug output ── + if(InpDebugMode) + PrintSignalReport(report, m5TA); + + // ── 7. Quality gate ── + if(!IsQualitySufficient(report.signalQuality)) return; + if(report.finalSignal == SS_NEUTRAL) return; + + // ── 8. Generate trade setup ── + TradeSetup setup; + bool setupValid = false; + + if(report.signalQuality == SQ_APLUS || report.signalQuality == SQ_A) + setupValid = GenerateXauAtrSetup(report, m5TA, setup); + + if(!setupValid) + setupValid = GenerateGenericSetup(report, m5TA, setup); + + // ── 9. Open order ── + if(setupValid) + { + PrintFormat("SIGNAL: %s | Quality=%s | Score=%.1f | Conf=%.1f | RR=%.2f", + setup.direction, QualityToString(report.signalQuality), + report.finalScore, report.finalConfidence, setup.rr); + OpenOrder(setup); + } +} +//+------------------------------------------------------------------+