//+------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+