//+------------------------------------------------------------------+
//| M5 Trend Pullback EA |
//| Exness / MT5 |
//| Forex + XAUUSD |
//| Risk: 0.5% per trade |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"
#property description "M5 EMA50/EMA200 + RSI + ATR Trend Pullback EA"
#include <Trade/Trade.mqh>
CTrade trade;
//==================================================================
// INPUTS
//==================================================================
//--- General
input ulong MagicNumber = 20260922;
input ENUM_TIMEFRAMES TradingTF = PERIOD_M5;
//--- Risk
input double RiskPercent = 0.50;
input double RewardRiskRatio = 2.00;
input double ATR_SL_Multiplier = 1.50;
//--- Indicators
input int FastEMA = 50;
input int SlowEMA = 200;
input int RSIPeriod = 14;
input int ATRPeriod = 14;
//--- Entry filters
input double RSI_Buy_Level = 50.0;
input double RSI_Sell_Level = 50.0;
input double PullbackToleranceATR = 0.50;
//--- Spread
input double MaxSpreadPoints = 40;
//--- Daily protection
input double MaxDailyLossPercent = 2.0;
input int MaxConsecutiveLosses = 3;
//--- Break-even
input bool UseBreakEven = true;
input double BreakEvenAtR = 1.0;
input double BreakEvenOffsetPoints = 5;
//--- Trading session
input bool UseSessionFilter = true;
input int StartHour = 7;
input int EndHour = 19;
//--- Execution
input int SlippagePoints = 20;
//==================================================================
// GLOBAL VARIABLES
//==================================================================
int emaFastHandle = INVALID_HANDLE;
int emaSlowHandle = INVALID_HANDLE;
int rsiHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
datetime lastBarTime = 0;
double dayStartEquity = 0.0;
int consecutiveLosses = 0;
int currentDay = -1;
//==================================================================
// INITIALIZATION
//==================================================================
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(SlippagePoints);
emaFastHandle = iMA(
_Symbol,
TradingTF,
FastEMA,
0,
MODE_EMA,
PRICE_CLOSE
);
emaSlowHandle = iMA(
_Symbol,
TradingTF,
SlowEMA,
0,
MODE_EMA,
PRICE_CLOSE
);
rsiHandle = iRSI(
_Symbol,
TradingTF,
RSIPeriod,
PRICE_CLOSE
);
atrHandle = iATR(
_Symbol,
TradingTF,
ATRPeriod
);
if(emaFastHandle == INVALID_HANDLE ||
emaSlowHandle == INVALID_HANDLE ||
rsiHandle == INVALID_HANDLE ||
atrHandle == INVALID_HANDLE)
{
Print("ERROR: Failed to create indicator handles.");
return(INIT_FAILED);
}
ResetDailyStats();
Print("M5 Trend Pullback EA initialized.");
Print("Symbol: ", _Symbol);
Print("Risk: ", DoubleToString(RiskPercent, 2), "%");
return(INIT_SUCCEEDED);
}
//==================================================================
// DEINITIALIZATION
//==================================================================
void OnDeinit(const int reason)
{
if(emaFastHandle != INVALID_HANDLE)
IndicatorRelease(emaFastHandle);
if(emaSlowHandle != INVALID_HANDLE)
IndicatorRelease(emaSlowHandle);
if(rsiHandle != INVALID_HANDLE)
IndicatorRelease(rsiHandle);
if(atrHandle != INVALID_HANDLE)
IndicatorRelease(atrHandle);
}
//==================================================================
// MAIN
//==================================================================
void OnTick()
{
UpdateDailyStats();
ManageBreakEven();
if(!IsNewBar())
return;
if(!TradingAllowed())
return;
if(HasOpenPosition())
return;
if(!SpreadIsAcceptable())
return;
if(!SessionAllowed())
return;
CheckForEntry();
}
//==================================================================
// NEW BAR DETECTION
//==================================================================
bool IsNewBar()
{
datetime currentBar = iTime(_Symbol, TradingTF, 0);
if(currentBar == 0)
return false;
if(currentBar != lastBarTime)
{
lastBarTime = currentBar;
return true;
}
return false;
}
//==================================================================
// ENTRY LOGIC
//==================================================================
void CheckForEntry()
{
double emaFast[3];
double emaSlow[3];
double rsi[3];
double atr[3];
double closePrice[3];
ArraySetAsSeries(emaFast, true);
ArraySetAsSeries(emaSlow, true);
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(atr, true);
ArraySetAsSeries(closePrice, true);
if(CopyBuffer(emaFastHandle, 0, 0, 3, emaFast) < 3)
return;
if(CopyBuffer(emaSlowHandle, 0, 0, 3, emaSlow) < 3)
return;
if(CopyBuffer(rsiHandle, 0, 0, 3, rsi) < 3)
return;
if(CopyBuffer(atrHandle, 0, 0, 3, atr) < 3)
return;
if(CopyClose(_Symbol, TradingTF, 0, 3, closePrice) < 3)
return;
// Use the LAST CLOSED candle: index 1
double price = closePrice[1];
double previousPrice = closePrice[2];
double currentATR = atr[1];
if(currentATR <= 0)
return;
//==============================================================
// BUY CONDITIONS
//==============================================================
bool bullishTrend =
price > emaSlow[1] &&
emaFast[1] > emaSlow[1];
bool pullbackBuy =
price <= emaFast[1] + (currentATR * PullbackToleranceATR);
bool rsiBuy =
rsi[2] <= RSI_Buy_Level &&
rsi[1] > RSI_Buy_Level;
bool bullishCandle =
price > previousPrice;
if(bullishTrend &&
pullbackBuy &&
rsiBuy &&
bullishCandle)
{
OpenBuy(currentATR);
return;
}
//==============================================================
// SELL CONDITIONS
//==============================================================
bool bearishTrend =
price < emaSlow[1] &&
emaFast[1] < emaSlow[1];
bool pullbackSell =
price >= emaFast[1] - (currentATR * PullbackToleranceATR);
bool rsiSell =
rsi[2] >= RSI_Sell_Level &&
rsi[1] < RSI_Sell_Level;
bool bearishCandle =
price < previousPrice;
if(bearishTrend &&
pullbackSell &&
rsiSell &&
bearishCandle)
{
OpenSell(currentATR);
return;
}
}
//==================================================================
// BUY
//==================================================================
void OpenBuy(double atr)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(ask <= 0)
return;
double stopDistance = atr * ATR_SL_Multiplier;
double sl = ask - stopDistance;
double tp = ask + (stopDistance * RewardRiskRatio);
AdjustStopsForBroker(ORDER_TYPE_BUY, ask, sl, tp);
double lot = CalculateLotSize(
ORDER_TYPE_BUY,
ask,
sl
);
if(lot <= 0)
return;
bool result = trade.Buy(
lot,
_Symbol,
ask,
sl,
tp,
"M5 Trend Pullback BUY"
);
if(result)
{
Print(
"BUY opened | Lot=",
DoubleToString(lot, 2),
" | SL=",
DoubleToString(sl, _Digits),
" | TP=",
DoubleToString(tp, _Digits)
);
}
else
{
Print("BUY failed. Error: ", GetLastError());
}
}
//==================================================================
// SELL
//==================================================================
void OpenSell(double atr)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(bid <= 0)
return;
double stopDistance = atr * ATR_SL_Multiplier;
double sl = bid + stopDistance;
double tp = bid - (stopDistance * RewardRiskRatio);
AdjustStopsForBroker(ORDER_TYPE_SELL, bid, sl, tp);
double lot = CalculateLotSize(
ORDER_TYPE_SELL,
bid,
sl
);
if(lot <= 0)
return;
bool result = trade.Sell(
lot,
_Symbol,
bid,
sl,
tp,
"M5 Trend Pullback SELL"
);
if(result)
{
Print(
"SELL opened | Lot=",
DoubleToString(lot, 2),
" | SL=",
DoubleToString(sl, _Digits),
" | TP=",
DoubleToString(tp, _Digits)
);
}
else
{
Print("SELL failed. Error: ", GetLastError());
}
}
//==================================================================
// LOT SIZE CALCULATION
//==================================================================
double CalculateLotSize(
ENUM_ORDER_TYPE orderType,
double entryPrice,
double stopLoss
)
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney =
equity * (RiskPercent / 100.0);
if(riskMoney <= 0)
return 0;
double tickSize =
SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue =
SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0 || tickValue <= 0)
return 0;
double priceDistance =
MathAbs(entryPrice - stopLoss);
double ticks =
priceDistance / tickSize;
double lossPerLot =
ticks * tickValue;
if(lossPerLot <= 0)
return 0;
double lot =
riskMoney / lossPerLot;
return NormalizeVolume(lot);
}
//==================================================================
// VOLUME NORMALIZATION
//==================================================================
double NormalizeVolume(double volume)
{
double minLot =
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot =
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep =
SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lotStep <= 0)
return 0;
volume =
MathFloor(volume / lotStep) * lotStep;
volume =
MathMax(volume, minLot);
volume =
MathMin(volume, maxLot);
int digits = 2;
if(lotStep == 0.1)
digits = 1;
if(lotStep == 0.01)
digits = 2;
if(lotStep == 0.001)
digits = 3;
return NormalizeDouble(volume, digits);
}
//==================================================================
// OPEN POSITION CHECK
//==================================================================
bool HasOpenPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket =
PositionGetTicket(i);
if(ticket == 0)
continue;
if(!PositionSelectByTicket(ticket))
continue;
string symbol =
PositionGetString(POSITION_SYMBOL);
long magic =
PositionGetInteger(POSITION_MAGIC);
if(symbol == _Symbol &&
magic == (long)MagicNumber)
{
return true;
}
}
return false;
}
//==================================================================
// SPREAD FILTER
//==================================================================
bool SpreadIsAcceptable()
{
double ask =
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid =
SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0 || bid <= 0)
return false;
double spreadPoints =
(ask - bid) / _Point;
if(spreadPoints > MaxSpreadPoints)
{
Print(
"Spread too high: ",
DoubleToString(spreadPoints, 1),
" points"
);
return false;
}
return true;
}
//==================================================================
// SESSION FILTER
//==================================================================
bool SessionAllowed()
{
if(!UseSessionFilter)
return true;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(StartHour < EndHour)
{
if(dt.hour >= StartHour &&
dt.hour < EndHour)
return true;
return false;
}
// Handles sessions crossing midnight
if(dt.hour >= StartHour ||
dt.hour < EndHour)
return true;
return false;
}
//==================================================================
// DAILY PROTECTION
//==================================================================
bool TradingAllowed()
{
if(dayStartEquity <= 0)
return false;
double equity =
AccountInfoDouble(ACCOUNT_EQUITY);
double dailyLossPercent =
((dayStartEquity - equity) /
dayStartEquity) * 100.0;
if(dailyLossPercent >= MaxDailyLossPercent)
{
Print("Daily loss limit reached.");
return false;
}
if(consecutiveLosses >= MaxConsecutiveLosses)
{
Print("Maximum consecutive losses reached.");
return false;
}
return true;
}
//==================================================================
// DAILY STATS
//==================================================================
void ResetDailyStats()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
currentDay = dt.day;
dayStartEquity =
AccountInfoDouble(ACCOUNT_EQUITY);
consecutiveLosses = 0;
}
void UpdateDailyStats()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.day != currentDay)
{
ResetDailyStats();
}
}
//==================================================================
// BREAK-EVEN
//==================================================================
void ManageBreakEven()
{
if(!UseBreakEven)
return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket =
PositionGetTicket(i);
if(ticket == 0)
continue;
if(!PositionSelectByTicket(ticket))
continue;
string symbol =
PositionGetString(POSITION_SYMBOL);
long magic =
PositionGetInteger(POSITION_MAGIC);
if(symbol != _Symbol ||
magic != (long)MagicNumber)
continue;
long type =
PositionGetInteger(POSITION_TYPE);
double openPrice =
PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL =
PositionGetDouble(POSITION_SL);
double currentTP =
PositionGetDouble(POSITION_TP);
double currentPrice;
if(type == POSITION_TYPE_BUY)
currentPrice =
SymbolInfoDouble(_Symbol, SYMBOL_BID);
else
currentPrice =
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(currentPrice <= 0)
continue;
double riskDistance =
MathAbs(openPrice - currentSL);
if(riskDistance <= 0)
continue;
if(type == POSITION_TYPE_BUY)
{
double profitDistance =
currentPrice - openPrice;
if(profitDistance >=
riskDistance * BreakEvenAtR)
{
double newSL =
openPrice +
BreakEvenOffsetPoints * _Point;
if(currentSL < newSL)
{
trade.PositionModify(
ticket,
NormalizeDouble(newSL, _Digits),
currentTP
);
}
}
}
if(type == POSITION_TYPE_SELL)
{
double profitDistance =
openPrice - currentPrice;
if(profitDistance >=
riskDistance * BreakEvenAtR)
{
double newSL =
openPrice -
BreakEvenOffsetPoints * _Point;
if(currentSL == 0 ||
currentSL > newSL)
{
trade.PositionModify(
ticket,
NormalizeDouble(newSL, _Digits),
currentTP
);
}
}
}
}
}
//==================================================================
// BROKER STOP LEVEL ADJUSTMENT
//==================================================================
void AdjustStopsForBroker(
ENUM_ORDER_TYPE orderType,
double entry,
double &sl,
double &tp
)
{
long stopLevel =
SymbolInfoInteger(
_Symbol,
SYMBOL_TRADE_STOPS_LEVEL
);
double minimumDistance =
stopLevel * _Point;
if(orderType == ORDER_TYPE_BUY)
{
if(entry - sl < minimumDistance)
sl = entry - minimumDistance;
if(tp - entry < minimumDistance)
tp = entry + minimumDistance;
}
if(orderType == ORDER_TYPE_SELL)
{
if(sl - entry < minimumDistance)
sl = entry + minimumDistance;
if(entry - tp < minimumDistance)
tp = entry - minimumDistance;
}
sl = NormalizeDouble(sl, _Digits);
tp = NormalizeDouble(tp, _Digits);
}
//==================================================================
// TRADE TRANSACTION
//==================================================================
void OnTradeTransaction(
const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result
)
{
if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
ulong dealTicket = trans.deal;
if(dealTicket == 0)
return;
if(!HistoryDealSelect(dealTicket))
return;
long magic =
HistoryDealGetInteger(
dealTicket,
DEAL_MAGIC
);
if(magic != (long)MagicNumber)
return;
long entryType =
HistoryDealGetInteger(
dealTicket,
DEAL_ENTRY
);
if(entryType != DEAL_ENTRY_OUT)
return;
double profit =
HistoryDealGetDouble(
dealTicket,
DEAL_PROFIT
);
double commission =
HistoryDealGetDouble(
dealTicket,
DEAL_COMMISSION
);
double swap =
HistoryDealGetDouble(
dealTicket,
DEAL_SWAP
);
double netResult =
profit + commission + swap;
if(netResult < 0)
{
consecutiveLosses++;
Print(
"Losing trade. Consecutive losses: ",
consecutiveLosses
);
}
else if(netResult > 0)
{
consecutiveLosses = 0;
Print(
"Winning trade. Consecutive losses reset."
);
}
}
//+------------------------------------------------------------------+