9a2b1f7b1e
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>
1961 lines
72 KiB
Plaintext
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);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|