Files
ForexBot2/ForexBot2_XAUUSD_M5.mq5
T
harwinder 9a2b1f7b1e Add MQL5 EA: 1:1 port of signal engine for XAU/USD M5 auto-trading
Ports the full Python signal pipeline to a single MQL5 Expert Advisor:
- 9 indicators (EMA, RSI, MACD, BB, ADX, ATR, Keltner/TTM, ROC, divergence,
  candlestick patterns) using built-in iMA/iRSI/iMACD/iBands/iADX/iATR handles
- Regime-adaptive composite scoring (trending/ranging/transitioning weights)
- MTF agreement across M5 + M15 + H1 confirmation timeframes
- Diminishing penalty cap system (30% max) with exhaustion block
- Quality grading (A+ through F) with SELL gate and transitioning boost
- ATR-based trade setup (A+/A) and swing-based generic setup (B/C)
- Max 1 position, breakeven management (0.15R trigger, 0.05R buffer)
- Session filters (blocked hours, weekend, allowed weekdays)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 20:49:05 -04:00

1961 lines
72 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| 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);
}
}
//+------------------------------------------------------------------+