//+------------------------------------------------------------------+
//| XAU_TrendBot |
//| Simple EMA Trend Following EA for XAUUSD |
//+------------------------------------------------------------------+
#property strict
#property copyright "Nathalie & Copilot"
#property link ""
#property version "1.00"
#property description "EMA trend following bot for XAUUSD M15"
input string InpSymbol = "XAUUSD"; // Symbol
input ENUM_TIMEFRAMES InpTF = PERIOD_M15; // Timeframe
input double RiskPercent = 1.0; // Risk per trade in %
input int FastEMAPeriod = 20; // Fast EMA
input int SlowEMAPeriod = 50; // Slow EMA
input int ATRPeriod = 14; // ATR period
input double ATRMultiplierSL = 2.0; // SL = ATR * multiplier
input double RR = 2.0; // Risk:Reward (TP = SL * RR)
input int MagicNumber = 202501; // Magic number
input bool UseSessionFilter = true; // Only trade in session
input int SessionStartHour = 7; // Start hour (server time)
input int SessionEndHour = 18; // End hour (server time)
//--- Global variables
MqlTick tick;
//+------------------------------------------------------------------+
//| Check if trading session is allowed |
//+------------------------------------------------------------------+
bool IsSessionAllowed()
{
if(!UseSessionFilter)
return(true);
datetime now = TimeCurrent();
int hour = TimeHour(now);
if(hour >= SessionStartHour && hour < SessionEndHour)
return(true);
return(false);
}
//+------------------------------------------------------------------+
//| Get open position for this symbol & magic |
//+------------------------------------------------------------------+
int GetOpenPositionType(string symbol, int magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == symbol &&
(int)PositionGetInteger(POSITION_MAGIC) == magic)
{
return((int)PositionGetInteger(POSITION_TYPE)); // POSITION_TYPE_BUY / SELL
}
}
}
return(-1); // no position
}
//+------------------------------------------------------------------+
//| Calculate lot size based on risk % and SL distance |
//+------------------------------------------------------------------+
double CalculateLotSize(string symbol, double riskPercent, double slPoints)
{
if(slPoints <= 0.0)
return(0.0);
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * (riskPercent / 100.0);
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double contractSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
if(tickSize <= 0.0 || tickValue <= 0.0 || contractSize <= 0.0)
return(0.0);
// Value per point for 1 lot
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double valuePerPointPerLot = (contractSize * point) / tickSize * tickValue;
double slValuePerLot = slPoints * valuePerPointPerLot;
if(slValuePerLot <= 0.0)
return(0.0);
double lots = riskMoney / slValuePerLot;
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
// Normalize to step
lots = MathFloor(lots / lotStep) * lotStep;
if(lots < minLot)
lots = minLot;
if(lots > maxLot)
lots = maxLot;
return(lots);
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
if(!SymbolInfoTick(InpSymbol, tick))
return;
if(!IsSessionAllowed())
return;
// Only operate on new bar
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(InpSymbol, InpTF, 0);
if(currentBarTime == lastBarTime)
return;
lastBarTime = currentBarTime;
// Get EMAs and ATR
int barsNeeded = MathMax(MathMax(FastEMAPeriod, SlowEMAPeriod), ATRPeriod) + 5;
if(Bars(InpSymbol, InpTF) < barsNeeded)
return;
double fastEMA[3], slowEMA[3], atr[3];
if(iMA(InpSymbol, InpTF, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, fastEMA) == INVALID_HANDLE)
return;
if(iMA(InpSymbol, InpTF, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, slowEMA) == INVALID_HANDLE)
return;
if(iATR(InpSymbol, InpTF, ATRPeriod, atr) == INVALID_HANDLE)
return;
// Actually copy values
if(CopyBuffer(iMA(InpSymbol, InpTF, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE), 0, 0, 3, fastEMA) <= 0)
return;
if(CopyBuffer(iMA(InpSymbol, InpTF, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE), 0, 0, 3, slowEMA) <= 0)
return;
if(CopyBuffer(iATR(InpSymbol, InpTF, ATRPeriod), 0, 0, 3, atr) <= 0)
return;
double fastCurr = fastEMA[0];
double slowCurr = slowEMA[0];
double atrCurr = atr[0];
double closePrice = iClose(InpSymbol, InpTF, 1); // last closed candle
// Check existing position
int posType = GetOpenPositionType(InpSymbol, MagicNumber);
double point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT);
// Calculate SL distance in points based on ATR
double slDistancePrice = atrCurr * ATRMultiplierSL;
double slDistancePoints = slDistancePrice / point;
// BUY condition
bool buySignal = (fastCurr > slowCurr && closePrice > fastCurr && closePrice > slowCurr);
// SELL condition
bool sellSignal = (fastCurr < slowCurr && closePrice < fastCurr && closePrice < slowCurr);
// No position: look for entries
if(posType == -1)
{
if(buySignal)
{
double slPrice = closePrice - slDistancePrice;
double tpPrice = closePrice + slDistancePrice * RR;
double lots = CalculateLotSize(InpSymbol, RiskPercent, slDistancePoints);
if(lots > 0.0)
{
tradeBuy(lots, slPrice, tpPrice);
}
}
else if(sellSignal)
{
double slPrice = closePrice + slDistancePrice;
double tpPrice = closePrice - slDistancePrice * RR;
double lots = CalculateLotSize(InpSymbol, RiskPercent, slDistancePoints);
if(lots > 0.0)
{
tradeSell(lots, slPrice, tpPrice);
}
}
}
}
//+------------------------------------------------------------------+
//| Helper: open buy |
//+------------------------------------------------------------------+
void tradeBuy(double lots, double slPrice, double tpPrice)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = InpSymbol;
request.volume = lots;
request.type = ORDER_TYPE_BUY;
request.price = SymbolInfoDouble(InpSymbol, SYMBOL_ASK);
request.sl = NormalizeDouble(slPrice, (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS));
request.tp = NormalizeDouble(tpPrice, (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS));
request.magic = MagicNumber;
request.deviation= 50;
request.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(request, result))
{
Print("Buy order failed: ", GetLastError());
}
else
{
if(result.retcode != TRADE_RETCODE_DONE)
Print("Buy order not executed: ", result.retcode);
}
}
//+------------------------------------------------------------------+
//| Helper: open sell |
//+------------------------------------------------------------------+
void tradeSell(double lots, double slPrice, double tpPrice)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = InpSymbol;
request.volume = lots;
request.type = ORDER_TYPE_SELL;
request.price = SymbolInfoDouble(InpSymbol, SYMBOL_BID);
request.sl = NormalizeDouble(slPrice, (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS));
request.tp = NormalizeDouble(tpPrice, (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS));
request.magic = MagicNumber;
request.deviation= 50;
request.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(request, result))
{
Print("Sell order failed: ", GetLastError());
}
else
{
if(result.retcode != TRADE_RETCODE_DONE)
Print("Sell order not executed: ", result.retcode);
}
}
//+------------------------------------------------------------------+