958 lines
32 KiB
Plaintext
958 lines
32 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.16"
|
|
#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 InpTrailingStart = 50; // Points of profit before trailing begins
|
|
input int InpTrailingStop = 30; // Trailing stop distance in points
|
|
input int InpTrailingStep = 10; // Minimum step to move SL
|
|
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)
|
|
|
|
input group "=== Weekend Protection ==="
|
|
input bool InpCloseBeforeWeekend = true; // Close positions Friday before market close
|
|
input int InpWeekendCloseHour = 17; // Hour to close (17 = 5 PM broker time)
|
|
|
|
//--- 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;
|
|
|
|
//--- Weekend Protection
|
|
bool weekendCloseExecuted = false;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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.16 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("Trailing Stop: ", InpUseTrailingStop ? "ON" : "OFF");
|
|
if(InpUseTrailingStop)
|
|
{
|
|
Print(" Trailing Start: ", InpTrailingStart, " pts");
|
|
Print(" Trailing Stop: ", InpTrailingStop, " pts");
|
|
Print(" Trailing Step: ", InpTrailingStep, " pts");
|
|
}
|
|
Print("Weekend Protection: ", InpCloseBeforeWeekend ? "ON" : "OFF");
|
|
if(InpCloseBeforeWeekend)
|
|
Print(" Close Hour: ", InpWeekendCloseHour, ":00 broker time");
|
|
|
|
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);
|
|
|
|
if(nearSupport || bouncedFromSupport)
|
|
{
|
|
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);
|
|
|
|
// FIXED: Removed restrictive belowPivot check - mirror buy logic
|
|
if(nearResistance || rejectedFromResistance)
|
|
{
|
|
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,
|
|
" | Bounce=", bouncedFromSupport, " Reject=", rejectedFromResistance);
|
|
}
|
|
|
|
//--- 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 - relaxed tolerances
|
|
if(X > A && A < B && B > C && C < D &&
|
|
ab_xa >= 0.3 && ab_xa <= 1.0 &&
|
|
bc_ab >= 0.3 && bc_ab <= 1.0 &&
|
|
cd_bc >= 0.7 && cd_bc <= 1.3)
|
|
{
|
|
buyCount++;
|
|
sources += "H ";
|
|
}
|
|
// Bearish AB=CD - relaxed tolerances
|
|
else if(X < A && A > B && B < C && C > D &&
|
|
ab_xa >= 0.3 && ab_xa <= 1.0 &&
|
|
bc_ab >= 0.3 && bc_ab <= 1.0 &&
|
|
cd_bc >= 0.7 && cd_bc <= 1.3)
|
|
{
|
|
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 weekend protection
|
|
if(!CheckWeekendProtection())
|
|
{
|
|
return; // Don't trade, weekend close executed
|
|
}
|
|
|
|
// Check for new bar
|
|
datetime currentBarTime = iTime(_Symbol, _Period, 0);
|
|
bool isNewBar = (currentBarTime != lastBarTime);
|
|
|
|
// Update trailing stops on every tick
|
|
if(InpUseTrailingStop)
|
|
{
|
|
UpdateTrailingStops();
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update trailing stops for all open positions |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateTrailingStops()
|
|
{
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double trailingStart = InpTrailingStart * point;
|
|
double trailingDist = InpTrailingStop * point;
|
|
double trailingStep = InpTrailingStep * point;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
|
|
// Only process positions for this EA
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
|
|
|
long posType = PositionGetInteger(POSITION_TYPE);
|
|
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double currentSL = PositionGetDouble(POSITION_SL);
|
|
double currentTP = PositionGetDouble(POSITION_TP);
|
|
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
bool modify = false;
|
|
double newSL = 0;
|
|
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
// For BUY: trailing stop below price
|
|
double profit = bid - openPrice;
|
|
if(profit >= trailingStart)
|
|
{
|
|
newSL = NormalizeDouble(bid - trailingDist, _Digits);
|
|
// Only move if new SL is better and meets step requirement
|
|
if(newSL > currentSL + trailingStep || currentSL == 0)
|
|
{
|
|
modify = true;
|
|
}
|
|
}
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL)
|
|
{
|
|
// For SELL: trailing stop above price
|
|
double profit = openPrice - ask;
|
|
if(profit >= trailingStart)
|
|
{
|
|
newSL = NormalizeDouble(ask + trailingDist, _Digits);
|
|
// Only move if new SL is better and meets step requirement
|
|
if(currentSL == 0 || newSL < currentSL - trailingStep)
|
|
{
|
|
modify = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(modify)
|
|
{
|
|
// Ensure SL is valid (not too close to current price)
|
|
double minDist = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
if(bid - newSL < minDist)
|
|
newSL = NormalizeDouble(bid - minDist, _Digits);
|
|
}
|
|
else
|
|
{
|
|
if(newSL - ask < minDist)
|
|
newSL = NormalizeDouble(ask + minDist, _Digits);
|
|
}
|
|
|
|
if(Trade.PositionModify(ticket, newSL, currentTP))
|
|
{
|
|
Print("✅ Trailing stop updated for #", ticket, " New SL: ", DoubleToString(newSL, _Digits));
|
|
}
|
|
else
|
|
{
|
|
Print("❌ Failed to update trailing stop for #", ticket, ": ", Trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close all positions for this EA |
|
|
//+------------------------------------------------------------------+
|
|
void CloseAllPositions(string reason)
|
|
{
|
|
int total = PositionsTotal();
|
|
int closed = 0;
|
|
|
|
for(int i = total - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
|
|
// Only close positions for this EA and symbol
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;
|
|
|
|
long posType = PositionGetInteger(POSITION_TYPE);
|
|
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
if(Trade.PositionClose(ticket))
|
|
closed++;
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL)
|
|
{
|
|
if(Trade.PositionClose(ticket))
|
|
closed++;
|
|
}
|
|
}
|
|
|
|
Print("📊 Closed ", closed, " positions for weekend protection. Reason: ", reason);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check Weekend Protection |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckWeekendProtection()
|
|
{
|
|
if(!InpCloseBeforeWeekend) return true;
|
|
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
|
|
// Only check on Friday
|
|
if(dt.day_of_week != FRIDAY)
|
|
{
|
|
// Reset flag on other days
|
|
if(weekendCloseExecuted)
|
|
weekendCloseExecuted = false;
|
|
return true;
|
|
}
|
|
|
|
// Already executed this Friday
|
|
if(weekendCloseExecuted) return true;
|
|
|
|
// Check if it's close to weekend close time
|
|
if(dt.hour >= InpWeekendCloseHour)
|
|
{
|
|
Print("⚠️ WEEKEND CLOSE: It's Friday ", dt.hour, ":00 - Closing all positions!");
|
|
SendNotification("WEEKEND CLOSE: Closing all positions before weekend");
|
|
|
|
// Close all open positions
|
|
CloseAllPositions("Weekend protection - Friday close");
|
|
|
|
weekendCloseExecuted = true;
|
|
return false; // Block new trades
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|