Files
mql-trading-bots/MultiSignal_Confluence_EA.mq5

775 lines
26 KiB
Plaintext

//+------------------------------------------------------------------+
//| MultiSignal_Confluence_EA.mq5 |
//| EA for MultiSignal Confluence Trading |
//| FIXED - Actually trades! |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Abbey Road Tech"
#property link "https://www.abbeyroadtech.com"
#property version "1.13"
#property strict
#include <Trade\Trade.mqh>
//--- Input Parameters
input group "=== Confluence Trading Settings ==="
input int InpMinConfluence = 2; // Min signals to trade (1-3)
input double InpMinStrength = 0.90; // Min signal strength (0.0-1.0) - HIGH for quality confluence trades
input bool InpRequireAllAgree = true; // All signals must agree
input group "=== Risk Management ==="
input double InpLotSize = 0.01; // Fixed Lot size (if UseRiskPercent=false)
input bool InpUseRiskPercent = true; // Use risk % for dynamic lot sizing
input double InpRiskPercent = 1.0; // Risk % per trade (1.0 = 1% of account)
input int InpStopLoss = 100; // Stop Loss in points
input int InpTakeProfit = 200; // Take Profit in points
input bool InpUseTrailingStop = true; // Use trailing stop
input int InpMaxPositions = 3; // Max concurrent positions per symbol
input group "=== Filters ==="
input bool InpUseTrendFilter = false; // Use MA trend filter (disable for more trades)
input int InpTrendMAPeriod = 50; // Trend MA period
input group "=== Volatility Filter (Anti-Chop) ==="
input bool InpUseVolatilityFilter = true; // Filter out low volatility / narrow bands
input int InpATRPeriod = 14; // ATR period for volatility measurement
input double InpMinATRPercent = 0.5; // Min ATR as % of price (0.5 = 0.5%)
input bool InpUseADXFilter = true; // Use ADX trend strength filter
input int InpADXPeriod = 14; // ADX period
input double InpMinADX = 20.0; // Min ADX to trade (20-25 recommended)
input group "=== EA Settings ==="
input ulong InpMagicNumber = 777777; // Magic number
input int InpSlippage = 3; // Max slippage
input bool InpDebugMode = true; // Print debug info
input group "=== Equity Protection ==="
input double InpMaxDailyDrawdown = 3.0; // Max daily drawdown % (0=disable)
//--- Global Variables
CTrade Trade;
int TrendMAHandle;
int ATRHandle;
int ADXHandle;
datetime lastBarTime = 0;
int totalBuyPositions = 0;
int totalSellPositions = 0;
//--- Daily Drawdown Protection
double dailyStartEquity = 0;
datetime lastEquityReset = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize trade object
Trade.SetExpertMagicNumber(InpMagicNumber);
Trade.SetDeviationInPoints(InpSlippage);
Trade.SetTypeFilling(ORDER_FILLING_IOC);
// Initialize trend filter
if(InpUseTrendFilter)
TrendMAHandle = iMA(_Symbol, _Period, InpTrendMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
// Initialize volatility filters
if(InpUseVolatilityFilter)
ATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
if(InpUseADXFilter)
ADXHandle = iADX(_Symbol, _Period, InpADXPeriod);
lastBarTime = iTime(_Symbol, _Period, 0);
Print("=== MultiSignal Confluence EA v1.12 Initialized ===");
Print("Symbol: ", _Symbol);
Print("Magic: ", InpMagicNumber);
Print("Min Confluence: ", InpMinConfluence);
Print("Min Strength: ", InpMinStrength);
Print("Volatility Filter: ", InpUseVolatilityFilter ? "ON" : "OFF");
Print("ADX Filter: ", InpUseADXFilter ? "ON" : "OFF");
Print("This EA trades DIRECTLY - no external indicator needed!");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(TrendMAHandle != INVALID_HANDLE)
IndicatorRelease(TrendMAHandle);
if(ATRHandle != INVALID_HANDLE)
IndicatorRelease(ATRHandle);
if(ADXHandle != INVALID_HANDLE)
IndicatorRelease(ADXHandle);
Print("EA Deinitialized for ", _Symbol);
}
//+------------------------------------------------------------------+
//| Get Symbol Point |
//+------------------------------------------------------------------+
double GetSymbolPoint()
{
// FIX: Use direct symbol info instead of CSymbolInfo to avoid cross-symbol contamination
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
// For forex pairs with 5 or 3 digits, point is 0.00001 or 0.001
// For JPY pairs, we need to handle correctly
if(_Digits == 3 || _Digits == 5)
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
return point;
}
//+------------------------------------------------------------------+
//| Get Ask Price (FIXED - uses _Symbol directly) |
//+------------------------------------------------------------------+
double GetAsk()
{
return SymbolInfoDouble(_Symbol, SYMBOL_ASK);
}
//+------------------------------------------------------------------+
//| Get Bid Price (FIXED - uses _Symbol directly) |
//+------------------------------------------------------------------+
double GetBid()
{
return SymbolInfoDouble(_Symbol, SYMBOL_BID);
}
//+------------------------------------------------------------------+
//| Count Open Positions |
//+------------------------------------------------------------------+
void CountOpenPositions()
{
totalBuyPositions = 0;
totalSellPositions = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionSelectByTicket(ticket))
{
// FIX: Only count positions for the CURRENT symbol this EA instance is running on
string posSymbol = PositionGetString(POSITION_SYMBOL);
if(posSymbol != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(posType == POSITION_TYPE_BUY)
totalBuyPositions++;
else if(posType == POSITION_TYPE_SELL)
totalSellPositions++;
}
}
}
}
//+------------------------------------------------------------------+
//| Check Trend Filter |
//+------------------------------------------------------------------+
bool CheckTrendFilter(int signalType)
{
if(!InpUseTrendFilter) return true;
if(TrendMAHandle == INVALID_HANDLE) return true;
double maValue[1];
if(CopyBuffer(TrendMAHandle, 0, 0, 1, maValue) <= 0) return true;
double close = iClose(_Symbol, _Period, 0);
if(signalType == 1 && close < maValue[0]) // Buy but below MA
{
if(InpDebugMode) Print("Trend filter BLOCKED BUY (price below MA)");
return false;
}
if(signalType == -1 && close > maValue[0]) // Sell but above MA
{
if(InpDebugMode) Print("Trend filter BLOCKED SELL (price above MA)");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check Volatility Filter (Anti-Chop) |
//+------------------------------------------------------------------+
bool CheckVolatilityFilter()
{
// Check ATR (volatility) filter
if(InpUseVolatilityFilter)
{
if(ATRHandle == INVALID_HANDLE)
{
if(InpDebugMode) Print("Volatility filter: ATR handle invalid, allowing trade");
return true;
}
double atrValue[1];
if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0)
{
if(InpDebugMode) Print("Volatility filter: Failed to get ATR, allowing trade");
return true;
}
double close = iClose(_Symbol, _Period, 0);
double atrPercent = (atrValue[0] / close) * 100;
if(atrPercent < InpMinATRPercent)
{
if(InpDebugMode) Print("Volatility filter BLOCKED: ATR too low (",
DoubleToString(atrPercent, 2), "% < ",
DoubleToString(InpMinATRPercent, 2), "%) - Narrow bands/choppy market");
return false;
}
}
// Check ADX (trend strength) filter
if(InpUseADXFilter)
{
if(ADXHandle == INVALID_HANDLE)
{
if(InpDebugMode) Print("ADX filter: Handle invalid, allowing trade");
return true;
}
double adxValue[1];
if(CopyBuffer(ADXHandle, 0, 0, 1, adxValue) <= 0)
{
if(InpDebugMode) Print("ADX filter: Failed to get ADX, allowing trade");
return true;
}
if(adxValue[0] < InpMinADX)
{
if(InpDebugMode) Print("ADX filter BLOCKED: Trend too weak (ADX ",
DoubleToString(adxValue[0], 1), " < ",
DoubleToString(InpMinADX, 1), ")");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Calculate Pivots |
//+------------------------------------------------------------------+
void CalculatePivots(double &p, double &r1, double &r2, double &s1, double &s2)
{
double prevHigh = iHigh(_Symbol, PERIOD_D1, 1);
double prevLow = iLow(_Symbol, PERIOD_D1, 1);
double prevClose = iClose(_Symbol, PERIOD_D1, 1);
p = (prevHigh + prevLow + prevClose) / 3.0;
r1 = (p * 2) - prevLow;
r2 = p + (prevHigh - prevLow);
s1 = (p * 2) - prevHigh;
s2 = p - (prevHigh - prevLow);
}
//+------------------------------------------------------------------+
//| Check for Signals |
//+------------------------------------------------------------------+
void CheckSignals(int &buyCount, int &sellCount, string &sources)
{
buyCount = 0;
sellCount = 0;
sources = "";
double close = iClose(_Symbol, _Period, 0);
double open = iOpen(_Symbol, _Period, 0);
double high = iHigh(_Symbol, _Period, 0);
double low = iLow(_Symbol, _Period, 0);
//--- PIVOT SIGNALS
double p, r1, r2, s1, s2;
CalculatePivots(p, r1, r2, s1, s2);
// Dynamic threshold based on symbol point value
double point = GetSymbolPoint();
double threshold = 0.0010; // 10 pips threshold for strong signals
// Calculate distance to each level
double distToS1 = MathAbs(close - s1);
double distToS2 = MathAbs(close - s2);
double distToR1 = MathAbs(close - r1);
double distToR2 = MathAbs(close - r2);
double distToP = MathAbs(close - p);
// Buy at support (price near support OR bounced off support)
bool nearSupport = (distToS1 < threshold) || (distToS2 < threshold);
bool bouncedFromSupport = (low <= s1 && close > s1) || (low <= s2 && close > s2) ||
(low <= s1 * 1.0005 && close > s1); // Slight buffer
bool abovePivot = close > p; // Above pivot = bullish context
if(nearSupport || (bouncedFromSupport && abovePivot))
{
buyCount++;
sources += "P" + IntegerToString((int)(distToS1 < distToS2 ? distToS1*10000 : distToS2*10000)) + " ";
}
// Sell at resistance (price near resistance OR rejected from resistance)
bool nearResistance = (distToR1 < threshold) || (distToR2 < threshold);
bool rejectedFromResistance = (high >= r1 && close < r1) || (high >= r2 && close < r2) ||
(high >= r1 * 0.9995 && close < r1); // Slight buffer
bool belowPivot = close < p; // Below pivot = bearish context
if(nearResistance || (rejectedFromResistance && belowPivot))
{
sellCount++;
sources += "P" + IntegerToString((int)(distToR1 < distToR2 ? distToR1*10000 : distToR2*10000)) + " ";
}
if(InpDebugMode && (nearSupport || nearResistance || bouncedFromSupport || rejectedFromResistance))
{
Print("Pivot Check: Close=", close, " P=", p, " R1=", r1, " S1=", s1,
" | DistS1=", distToS1, " DistR1=", distToR1,
" | AboveP=", abovePivot, " BelowP=", belowPivot);
}
//--- CANDLESTICK SIGNALS
double body = MathAbs(close - open);
double range = high - low;
double upperWick = high - MathMax(open, close);
double lowerWick = MathMin(open, close) - low;
if(range > 0)
{
// Bullish Hammer
if(close > open && lowerWick > body * 2 && upperWick < body * 0.5 && lowerWick / range > 0.3)
{
buyCount++;
sources += "C ";
}
// Bearish Shooting Star
else if(close < open && upperWick > body * 2 && lowerWick < body * 0.5 && upperWick / range > 0.3)
{
sellCount++;
sources += "C ";
}
// Bullish Engulfing
else if(iClose(_Symbol, _Period, 1) < iOpen(_Symbol, _Period, 1) && close > open &&
open <= iClose(_Symbol, _Period, 1) && close >= iOpen(_Symbol, _Period, 1))
{
buyCount++;
sources += "C ";
}
// Bearish Engulfing
else if(iClose(_Symbol, _Period, 1) > iOpen(_Symbol, _Period, 1) && close < open &&
open >= iClose(_Symbol, _Period, 1) && close <= iOpen(_Symbol, _Period, 1))
{
sellCount++;
sources += "C ";
}
}
//--- HARMONIC SIGNALS (simplified)
if(iBars(_Symbol, _Period) > 5)
{
double X = iHigh(_Symbol, _Period, 4);
double A = iLow(_Symbol, _Period, 3);
double B = iHigh(_Symbol, _Period, 2);
double C = iLow(_Symbol, _Period, 1);
double D = close;
double XA = MathAbs(A - X);
double AB = MathAbs(B - A);
double BC = MathAbs(C - B);
double CD = MathAbs(D - C);
if(XA > 0 && AB > 0 && BC > 0)
{
double ab_xa = AB / XA;
double bc_ab = BC / AB;
double cd_bc = CD / BC;
// Bullish AB=CD
if(X > A && A < B && B > C && C < D &&
ab_xa >= 0.5 && ab_xa <= 0.8 &&
bc_ab >= 0.5 && bc_ab <= 0.8 &&
cd_bc >= 0.9 && cd_bc <= 1.1)
{
buyCount++;
sources += "H ";
}
// Bearish AB=CD
else if(X < A && A > B && B < C && C > D &&
ab_xa >= 0.5 && ab_xa <= 0.8 &&
bc_ab >= 0.5 && bc_ab <= 0.8 &&
cd_bc >= 0.9 && cd_bc <= 1.1)
{
sellCount++;
sources += "H ";
}
}
}
}
//+------------------------------------------------------------------+
//| Calculate dynamic lot size based on risk % |
//+------------------------------------------------------------------+
double CalculateLotSize(double slPoints)
{
if(!InpUseRiskPercent) return InpLotSize;
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(tickSize <= 0 || slPoints <= 0) return InpLotSize;
// Calculate risk amount
double riskAmount = accountBalance * InpRiskPercent / 100.0;
// Calculate lot size: RiskAmount / (SL_Points * TickValue / TickSize)
double lots = riskAmount / (slPoints * tickValue / tickSize);
// Normalize to lot step
lots = MathFloor(lots / lotStep) * lotStep;
// Apply min/max limits
lots = MathMax(minLot, MathMin(maxLot, lots));
Print("Risk Lot Calc: Balance=", accountBalance, " Risk%=", InpRiskPercent,
" SL=", slPoints, " Lots=", lots);
return lots;
}
//+------------------------------------------------------------------+
//| Validate SL/TP Prices |
//+------------------------------------------------------------------+
bool ValidatePrices(double &sl, double &tp, double price, bool isBuy)
{
double point = GetSymbolPoint();
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
// Ensure minimum distance from price
if(isBuy)
{
// For BUY: SL must be below price, TP above
double minSL = price - minStopLevel;
double maxTP = price + minStopLevel;
if(sl > minSL)
{
sl = minSL - point;
Print("SL adjusted to minimum distance for BUY: ", DoubleToString(sl, _Digits));
}
if(tp < maxTP)
{
tp = maxTP + point;
Print("TP adjusted to minimum distance for BUY: ", DoubleToString(tp, _Digits));
}
}
else
{
// For SELL: SL must be above price, TP below
double minSL = price + minStopLevel;
double maxTP = price - minStopLevel;
if(sl < minSL)
{
sl = minSL + point;
Print("SL adjusted to minimum distance for SELL: ", DoubleToString(sl, _Digits));
}
if(tp > maxTP)
{
tp = maxTP - point;
Print("TP adjusted to minimum distance for SELL: ", DoubleToString(tp, _Digits));
}
}
// Sanity check - ensure SL and TP are reasonable
double priceDiffSL = MathAbs(sl - price);
double priceDiffTP = MathAbs(tp - price);
double maxDiff = 10000 * point; // Max 10000 pips
if(priceDiffSL > maxDiff || priceDiffTP > maxDiff)
{
Print("ERROR: SL/TP prices seem invalid! SL:", DoubleToString(sl, _Digits),
" TP:", DoubleToString(tp, _Digits), " Price:", DoubleToString(price, _Digits));
return false;
}
// Check for frozen/stale prices (possible cross-symbol contamination)
double currentAsk = GetAsk();
double currentBid = GetBid();
double priceDiff = MathAbs(price - (isBuy ? currentAsk : currentBid));
if(priceDiff > 100 * point) // If price is more than 100 pips different
{
Print("WARNING: Price mismatch detected! Using:", DoubleToString(price, _Digits),
" Current:", DoubleToString(isBuy ? currentAsk : currentBid, _Digits));
// Update to current price
price = isBuy ? currentAsk : currentBid;
// Recalculate SL/TP
if(isBuy)
{
sl = price - InpStopLoss * point;
tp = price + InpTakeProfit * point;
}
else
{
sl = price + InpStopLoss * point;
tp = price - InpTakeProfit * point;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Open Buy Position (FIXED) |
//+------------------------------------------------------------------+
void OpenBuyPosition(double strength, string sources)
{
// FIX: Use direct symbol functions instead of CSymbolInfo
double ask = GetAsk();
double bid = GetBid();
double point = GetSymbolPoint();
if(ask <= 0 || bid <= 0)
{
Print("ERROR: Invalid prices for ", _Symbol, " Ask:", ask, " Bid:", bid);
return;
}
double lotSize = CalculateLotSize(InpStopLoss);
double sl = ask - InpStopLoss * point;
double tp = ask + InpTakeProfit * point;
// Validate and adjust prices
if(!ValidatePrices(sl, tp, ask, true))
{
Print("ERROR: Price validation failed for BUY on ", _Symbol);
return;
}
string comment = "Conf BUY (" + sources + ")";
Print("Attempting BUY on ", _Symbol, " Ask:", DoubleToString(ask, _Digits),
" SL:", DoubleToString(sl, _Digits), " TP:", DoubleToString(tp, _Digits));
if(Trade.Buy(lotSize, _Symbol, ask, sl, tp, comment))
{
Print("✅ BUY executed on ", _Symbol, "! Strength: ", DoubleToString(strength, 2),
" Sources: ", sources, " Lots: ", lotSize);
}
else
{
Print("❌ BUY failed on ", _Symbol, ": ", Trade.ResultRetcodeDescription(),
" (code: ", Trade.ResultRetcode(), ")");
}
}
//+------------------------------------------------------------------+
//| Open Sell Position (FIXED) |
//+------------------------------------------------------------------+
void OpenSellPosition(double strength, string sources)
{
// FIX: Use direct symbol functions instead of CSymbolInfo
double ask = GetAsk();
double bid = GetBid();
double point = GetSymbolPoint();
if(ask <= 0 || bid <= 0)
{
Print("ERROR: Invalid prices for ", _Symbol, " Ask:", ask, " Bid:", bid);
return;
}
double lotSize = CalculateLotSize(InpStopLoss);
double sl = bid + InpStopLoss * point;
double tp = bid - InpTakeProfit * point;
// Validate and adjust prices
if(!ValidatePrices(sl, tp, bid, false))
{
Print("ERROR: Price validation failed for SELL on ", _Symbol);
return;
}
string comment = "Conf SELL (" + sources + ")";
Print("Attempting SELL on ", _Symbol, " Bid:", DoubleToString(bid, _Digits),
" SL:", DoubleToString(sl, _Digits), " TP:", DoubleToString(tp, _Digits));
if(Trade.Sell(lotSize, _Symbol, bid, sl, tp, comment))
{
Print("✅ SELL executed on ", _Symbol, "! Strength: ", DoubleToString(strength, 2),
" Sources: ", sources, " Lots: ", lotSize);
}
else
{
Print("❌ SELL failed on ", _Symbol, ": ", Trade.ResultRetcodeDescription(),
" (code: ", Trade.ResultRetcode(), ")");
}
}
//+------------------------------------------------------------------+
//| Check Daily Drawdown Protection |
//+------------------------------------------------------------------+
bool CheckDailyDrawdown()
{
static bool warningPrinted = false;
if(InpMaxDailyDrawdown <= 0)
return true; // Protection disabled
datetime today = TimeCurrent() / 86400 * 86400;
if(today != lastEquityReset)
{
// New day - reset equity tracking
dailyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
lastEquityReset = today;
warningPrinted = false; // Reset warning for new day
Print("Daily equity reset: $", DoubleToString(dailyStartEquity, 2));
return true;
}
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
if(dailyStartEquity <= 0)
return true;
double drawdownPercent = (dailyStartEquity - currentEquity) / dailyStartEquity * 100;
bool overLimit = (drawdownPercent >= InpMaxDailyDrawdown);
if(overLimit)
{
if(!warningPrinted)
{
Print("⚠️ DAILY DRAWDOWN LIMIT REACHED: ", DoubleToString(drawdownPercent, 2),
"% (Limit: ", InpMaxDailyDrawdown, "%)");
warningPrinted = true;
}
return false; // Block new trades
}
else
{
warningPrinted = false; // Reset when below limit
}
return true;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Check daily drawdown limit first
if(!CheckDailyDrawdown())
{
return; // Don't trade if daily limit reached
}
// Check for new bar
datetime currentBarTime = iTime(_Symbol, _Period, 0);
bool isNewBar = (currentBarTime != lastBarTime);
// Update trailing stops on every tick
if(InpUseTrailingStop && !isNewBar)
{
// Simple trailing stop logic can be added here
}
// Only process on new bar
if(!isNewBar)
return;
lastBarTime = currentBarTime;
// Count positions (only for current symbol)
CountOpenPositions();
// Check max positions
if(totalBuyPositions + totalSellPositions >= InpMaxPositions)
{
if(InpDebugMode) Print(_Symbol, " Max positions reached (",
totalBuyPositions + totalSellPositions, "/", InpMaxPositions, ")");
return;
}
// Check for signals
int buyCount = 0, sellCount = 0;
string sources = "";
CheckSignals(buyCount, sellCount, sources);
if(InpDebugMode)
Print(_Symbol, " Bar ", TimeToString(currentBarTime), " Buy: ", buyCount, " Sell: ", sellCount, " Sources: ", sources);
// Calculate strength (FIXED: handle conflicting signals properly)
double strength = 0;
if(buyCount > 0 && sellCount == 0)
strength = 0.6 + (buyCount * 0.15); // Pure buy signals
else if(sellCount > 0 && buyCount == 0)
strength = -(0.6 + (sellCount * 0.15)); // Pure sell signals
else if(buyCount > 0 && sellCount > 0)
{
// Conflicting signals - only trade if one side is clearly stronger
if(buyCount > sellCount + 1)
strength = 0.6 + (buyCount * 0.15); // Buy dominates
else if(sellCount > buyCount + 1)
strength = -(0.6 + (sellCount * 0.15)); // Sell dominates
else
strength = 0; // Too conflicting
}
if(strength > 1.0) strength = 1.0;
if(strength < -1.0) strength = -1.0;
// Check minimum strength
if(MathAbs(strength) < InpMinStrength)
{
if(InpDebugMode) Print(_Symbol, " Strength too low: ", DoubleToString(MathAbs(strength), 2), " < ", InpMinStrength);
return;
}
// Check volatility filter (prevents trading in narrow bands / choppy markets)
if(!CheckVolatilityFilter())
return;
// BUY Signal
if(buyCount >= InpMinConfluence && (!InpRequireAllAgree || sellCount == 0))
{
if(!CheckTrendFilter(1))
return;
if(totalBuyPositions < InpMaxPositions)
{
Print("🟢 CONFLUENCE BUY on ", _Symbol, "! Signals: ", buyCount, " Strength: ", DoubleToString(strength, 2));
OpenBuyPosition(strength, sources);
}
}
// SELL Signal
else if(sellCount >= InpMinConfluence && (!InpRequireAllAgree || buyCount == 0))
{
if(!CheckTrendFilter(-1))
return;
if(totalSellPositions < InpMaxPositions)
{
Print("🔴 CONFLUENCE SELL on ", _Symbol, "! Signals: ", sellCount, " Strength: ", DoubleToString(MathAbs(strength), 2));
OpenSellPosition(MathAbs(strength), sources);
}
}
}
//+------------------------------------------------------------------+