EURUSD Trend Following EA MQL5: Multi-Timeframe Confirmation Bot
Review an educational EURUSD-style EA combining completed H1 EMA crossovers and ADX with the slope between completed H4 EMA samples. The source includes fixed lots, ATR stops, a holding-time limit and broker-server-hour entry filtering. No verified backtest, false-signal reduction or typical trend-cycle claim is supplied.
Strategy logic
Entry conditions
On a new H1 bar, compare completed H1 bars 1 and 2, require ADX at or above the configured threshold, and compare completed H4 EMA values at shifts 1 and 6. Entry also requires the server-time session and spread checks. There is no news calendar, extra one-hour waiting window or cross-timeframe optimization proof. Fixed Lots does not implement a fixed account-risk percentage.
Exit conditions
Initial stops are AtrMultSL times ATR and targets 3 times ATR. On each new H1 bar, expired owned positions are checked by their actual POSITION_TIME and trailing levels use completed H1 ATR. Opposite H1 crosses request closure independently of the entry session/spread gate. Trailing distance is AtrTrailStep times ATR, with a two-point minimum tightening, not fixed 0.5-ATR step increments. Checks wait for ticks and a new H1 bar; no exact London-open exit or guaranteed loss cap is provided.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| EURUSD_TrendFollowingEA.mq5 |
//| Pineify – EURUSD Multi-Timeframe Trend Following EA |
//| Strategy: H4 200 EMA bias + H1 20/50 EMA cross + ADX filter |
//| DISCLAIMER: For educational purposes only. Past performance |
//| does not guarantee future results. Trade at your own risk. |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property version "1.00"
#include <Trade/Trade.mqh>
CTrade Trade;
//--- Input parameters
input int MagicNumber = 123456;
input double Lots = 0.10;
input int FastEmaPeriod = 20; // H1 Fast EMA period
input int SlowEmaPeriod = 50; // H1 Slow EMA period
input int TrendEmaPeriod = 200; // H4 Trend EMA period
input int AdxPeriod = 14; // ADX confirmation period
input double AdxThreshold = 25.0; // Minimum ADX for entry
input int AtrPeriod = 14; // ATR period for stops
input double AtrMultSL = 1.2; // ATR multiplier for stop loss
input double AtrMultTrail = 1.5; // ATR multiplier to activate trailing
input double AtrTrailStep = 0.5; // ATR trailing step increment
input int MaxSpreadPoints = 20; // Max allowed spread in points
input int MaxTradeHours = 48; // Max position hold time in hours
input bool UseSessionFilter = true; // Apply configured server-hour entry window
input string SessionStart = "07:00"; // Broker-server session start
input string SessionEnd = "18:00"; // Broker-server session end
input int Slippage = 10; // Max slippage in points
//--- Global indicator handles
int g_hFastEmaH1 = INVALID_HANDLE;
int g_hSlowEmaH1 = INVALID_HANDLE;
int g_hTrendEmaH4 = INVALID_HANDLE;
int g_hAdxH1 = INVALID_HANDLE;
int g_hAtrH1 = INVALID_HANDLE;
//--- Buffer arrays
double g_fastEmaBuf[], g_slowEmaBuf[], g_trendEmaBuf[];
double g_adxBuf[], g_atrBuf[];
//--- Tracking
datetime g_lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
int sh,sm,eh,em;
if(FastEmaPeriod<1 || SlowEmaPeriod<=FastEmaPeriod || TrendEmaPeriod<1 || AdxPeriod<1 || AtrPeriod<1 || Lots<=0 || AtrMultSL<=0 || AtrMultTrail<0 || AtrTrailStep<=0 || MaxTradeHours<=0 || MaxSpreadPoints<0 || Slippage<0) return INIT_PARAMETERS_INCORRECT;
if(UseSessionFilter && (!ParseSessionTime(SessionStart,sh,sm) || !ParseSessionTime(SessionEnd,eh,em))) return INIT_PARAMETERS_INCORRECT;
Trade.SetExpertMagicNumber(MagicNumber);
Trade.SetDeviationInPoints(Slippage);
Trade.SetTypeFillingBySymbol(_Symbol);
//--- Create indicator handles for both timeframes
g_hFastEmaH1 = iMA(_Symbol, PERIOD_H1, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_hSlowEmaH1 = iMA(_Symbol, PERIOD_H1, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_hTrendEmaH4 = iMA(_Symbol, PERIOD_H4, TrendEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_hAdxH1 = iADX(_Symbol, PERIOD_H1, AdxPeriod);
g_hAtrH1 = iATR(_Symbol, PERIOD_H1, AtrPeriod);
//--- Validate all handles
if(g_hFastEmaH1 == INVALID_HANDLE ||
g_hSlowEmaH1 == INVALID_HANDLE ||
g_hTrendEmaH4 == INVALID_HANDLE ||
g_hAdxH1 == INVALID_HANDLE ||
g_hAtrH1 == INVALID_HANDLE)
{
Print("ERROR: Failed to create indicator handles.");
return INIT_FAILED;
}
//--- Set buffer series flag
ArraySetAsSeries(g_fastEmaBuf, true);
ArraySetAsSeries(g_slowEmaBuf, true);
ArraySetAsSeries(g_trendEmaBuf, true);
ArraySetAsSeries(g_adxBuf, true);
ArraySetAsSeries(g_atrBuf, true);
Print("EURUSD_TrendFollowingEA initialized on ", _Symbol);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_hFastEmaH1 != INVALID_HANDLE) IndicatorRelease(g_hFastEmaH1);
if(g_hSlowEmaH1 != INVALID_HANDLE) IndicatorRelease(g_hSlowEmaH1);
if(g_hTrendEmaH4 != INVALID_HANDLE) IndicatorRelease(g_hTrendEmaH4);
if(g_hAdxH1 != INVALID_HANDLE) IndicatorRelease(g_hAdxH1);
if(g_hAtrH1 != INVALID_HANDLE) IndicatorRelease(g_hAtrH1);
Print("EURUSD_TrendFollowingEA deinitialized. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Get single buffer value at shift index |
//+------------------------------------------------------------------+
double GetBufferValue(int handle, int bufferIndex, int shift)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(handle, bufferIndex, shift, 1, buf) < 1)
return EMPTY_VALUE;
return buf[0];
}
//+------------------------------------------------------------------+
//| Check if a new bar has formed on H1 |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime curBar = iTime(_Symbol, PERIOD_H1, 0);
if(curBar == 0) return false;
if(curBar == g_lastBarTime) return false;
g_lastBarTime = curBar;
return true;
}
//+------------------------------------------------------------------+
//| Check if current time is within trading session |
//+------------------------------------------------------------------+
bool IsInSession()
{
if(!UseSessionFilter) return true;
MqlDateTime dt;
if(!TimeToStruct(TimeCurrent(), dt)) return false;
int nowMinutes = dt.hour * 60 + dt.min;
int sH = 0, sM = 0, eH = 0, eM = 0;
if(!ParseSessionTime(SessionStart,sH,sM) || !ParseSessionTime(SessionEnd,eH,eM)) return false;
int startMinutes = sH * 60 + sM;
int endMinutes = eH * 60 + eM;
if(startMinutes==endMinutes) return false;
return startMinutes<endMinutes ? (nowMinutes>=startMinutes && nowMinutes<endMinutes) : (nowMinutes>=startMinutes || nowMinutes<endMinutes);
}
//+------------------------------------------------------------------+
//| Parse "HH:MM" string to hour and minute |
//+------------------------------------------------------------------+
bool ParseSessionTime(string str, int &hour, int &min)
{
if(StringLen(str)!=5 || StringSubstr(str,2,1)!=":") return false;
for(int i=0;i<5;i++) if(i!=2 && (StringGetCharacter(str,i)<48 || StringGetCharacter(str,i)>57)) return false;
hour = (int)StringToInteger(StringSubstr(str, 0, 2));
min = (int)StringToInteger(StringSubstr(str, 3, 2));
return hour>=0 && hour<24 && min>=0 && min<60;
}
//+------------------------------------------------------------------+
//| Check spread is within limits |
//+------------------------------------------------------------------+
bool SpreadOk()
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return false;
double spread = (tick.ask - tick.bid) / SymbolInfoDouble(_Symbol, SYMBOL_POINT);
return (spread <= MaxSpreadPoints);
}
//+------------------------------------------------------------------+
//| Count positions for this EA |
//+------------------------------------------------------------------+
int CountPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
count++;
}
return count;
}
//+------------------------------------------------------------------+
//| Count positions by type |
//+------------------------------------------------------------------+
int CountPositionsByType(ENUM_POSITION_TYPE type)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type) continue;
count++;
}
return count;
}
// Close expired owned positions using persistent terminal position time.
void CloseExpiredPositions()
{
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong ticket=PositionGetTicket(i);
if(ticket==0 || PositionGetString(POSITION_SYMBOL)!=_Symbol ||
PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
if(TimeCurrent()-(datetime)PositionGetInteger(POSITION_TIME)<(long)MaxTradeHours*3600) continue;
if(!Trade.PositionClose(ticket,Slippage) || Trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Expired close result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
//| Close all positions of a given type |
//+------------------------------------------------------------------+
void CloseAllByType(ENUM_POSITION_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type) continue;
if(!Trade.PositionClose(ticket,Slippage) || Trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Close result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
//| Apply trailing stop to open positions |
//+------------------------------------------------------------------+
void TrailingStops()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
double atrVal = GetBufferValue(g_hAtrH1, 0, 1);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(atrVal == EMPTY_VALUE || atrVal <= 0) continue;
ENUM_POSITION_TYPE pType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(pType == POSITION_TYPE_BUY)
{
double trailLevel = openPrice + AtrMultTrail * atrVal;
if(bid > trailLevel)
{
double newSL = NormalizeDouble(bid - AtrTrailStep * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
if(newSL > currentSL + point * 2)
{
if(!Trade.PositionModify(ticket,newSL,currentTP) || Trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Modify result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
}
else if(pType == POSITION_TYPE_SELL)
{
double trailLevel = openPrice - AtrMultTrail * atrVal;
if(ask < trailLevel)
{
double newSL = NormalizeDouble(ask + AtrTrailStep * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
if(newSL < currentSL - point * 2 || currentSL == 0)
{
if(!Trade.PositionModify(ticket,newSL,currentTP) || Trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Modify result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
}
}
}
//+------------------------------------------------------------------+
//| Check if H4 trend EMA is sloping upward |
//+------------------------------------------------------------------+
bool TrendEmaUp()
{
double curr = GetBufferValue(g_hTrendEmaH4, 0, 1);
double prev = GetBufferValue(g_hTrendEmaH4, 0, 6);
if(curr == EMPTY_VALUE || prev == EMPTY_VALUE) return false;
return (curr > prev);
}
//+------------------------------------------------------------------+
//| Check if H4 trend EMA is sloping downward |
//+------------------------------------------------------------------+
bool TrendEmaDown()
{
double curr = GetBufferValue(g_hTrendEmaH4, 0, 1);
double prev = GetBufferValue(g_hTrendEmaH4, 0, 6);
if(curr == EMPTY_VALUE || prev == EMPTY_VALUE) return false;
return (curr < prev);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Only process on new H1 bar
if(!IsNewBar()) return;
CloseExpiredPositions();
if(CountPositions()>0) TrailingStops();
//--- Read indicator values from last closed bar (shift=1)
double fastEma0 = GetBufferValue(g_hFastEmaH1, 0, 1);
double slowEma0 = GetBufferValue(g_hSlowEmaH1, 0, 1);
double fastEma1 = GetBufferValue(g_hFastEmaH1, 0, 2);
double slowEma1 = GetBufferValue(g_hSlowEmaH1, 0, 2);
double adxVal = GetBufferValue(g_hAdxH1, 0, 1);
double atrVal = GetBufferValue(g_hAtrH1, 0, 1);
if(fastEma0 == EMPTY_VALUE || slowEma0 == EMPTY_VALUE ||
fastEma1 == EMPTY_VALUE || slowEma1 == EMPTY_VALUE ||
adxVal == EMPTY_VALUE || atrVal == EMPTY_VALUE || atrVal<=0)
return;
//--- Detect EMA crossovers on H1
bool bullCross = (fastEma1 <= slowEma1) && (fastEma0 > slowEma0);
bool bearCross = (fastEma1 >= slowEma1) && (fastEma0 < slowEma0);
//--- Trend direction from H4 200 EMA
bool trendUp = TrendEmaUp();
bool trendDown = TrendEmaDown();
//--- Trend strength from ADX
bool trendStrong = (adxVal >= AdxThreshold);
//--- Determine entry bias
bool entryLong = bullCross && trendUp && trendStrong;
bool entryShort = bearCross && trendDown && trendStrong;
//--- Exit on EMA crossover reversal
if(bearCross && CountPositionsByType(POSITION_TYPE_BUY) > 0)
CloseAllByType(POSITION_TYPE_BUY);
if(bullCross && CountPositionsByType(POSITION_TYPE_SELL) > 0)
CloseAllByType(POSITION_TYPE_SELL);
if(!IsInSession() || !SpreadOk()) return;
//--- Entry
if(entryLong && CountPositions() == 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizeDouble(ask - AtrMultSL * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
double tp = NormalizeDouble(ask + 3.0 * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
if(Trade.Buy(Lots, _Symbol, ask, sl, tp, "EURUSD Trend Buy") && Trade.ResultRetcode()==TRADE_RETCODE_DONE)
{
Print("BUY request completed at ", ask, " SL=", sl, " TP=", tp);
}
else Print("Buy result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
if(entryShort && CountPositions() == 0)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = NormalizeDouble(bid + AtrMultSL * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
double tp = NormalizeDouble(bid - 3.0 * atrVal, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
if(Trade.Sell(Lots, _Symbol, bid, sl, tp, "EURUSD Trend Sell") && Trade.ResultRetcode()==TRADE_RETCODE_DONE)
{
Print("SELL request completed at ", bid, " SL=", sl, " TP=", tp);
}
else Print("Sell result: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom EURUSD trend-following EA
Describe the entry, exit, position, order, and risk rules you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Frequently Asked Questions
Related MQL5 pages
Technical references
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.