FTMO EA MQL5: Rule Verification and Risk-Guard Example
This tutorial examines an EMA/ADX EA with an illustrative equity guard. It is not an FTMO-approved or compliance-certified EA. FTMO products have different loss formulas, reset times and holding rules; a server-day equity snapshot and an intraday equity peak do not implement those rules. No source supports the former challenge-run, funded-account or performance claims. Use the example to inspect limitations, then verify the exact current account terms and test a separately specified implementation.
Strategy logic
Entry conditions
The single-symbol example checks completed H1 EMA 20/50 crossovers, completed H4 EMA 200 slope between bars 1 and 6, ADX at least 25 and a point spread cap. It compares completed ATR with the mean of the previous five completed ATR values. These filters are illustrative and are not an economic-calendar/news restriction. Requested size estimates a balance percentage using stop distance and tick value; if broker minimum volume would exceed the estimate, no order is requested. Costs, fills and account-wide positions remain separate concerns.
Exit conditions
Opposite crossovers, initial ATR SL/TP and tick-based trailing manage this symbol and magic number only. The example daily guard measures loss from equity sampled at the first server-date tick, while the total guard measures drawdown from the highest equity observed since initialization. A hit latches new-entry suspension and retries matching position closures; the daily latch resets next server day, while the total latch lasts until reinitialization. These in-memory states are not durable across restart and are not FTMO formulas. The optional Friday server-hour cutoff also blocks weekend entries, but requires ticks and successful fills. No 72-hour exit, CE(S)T conversion or complete account-level compliance monitor is included.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| FTMO_CompatibleEA.mq5 |
//| Educational risk guard; not FTMO approval or certification |
//| Strategy: H4 EMA slope + H1 EMA cross + illustrative equity guard |
//| 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;
//--- Illustrative guard parameters, not FTMO rule formulas
input int MagicNumber = 202618;
input double DailyLossLimitPct = 4.0; // Loss from first server-date equity snapshot
input double TotalDDLimitPct = 8.0; // Drawdown from observed equity peak since init
input bool CloseOnFriday = true; // Auto-close before weekend
input int FridayCloseHour = 21; // Broker-server hour for Friday close
input double RiskPerTradePct = 0.5; // Risk per trade (% of balance)
//--- Strategy Parameters
input int FastEmaPeriod = 20;
input int SlowEmaPeriod = 50;
input int TrendEmaPeriod = 200;
input int AdxPeriod = 14;
input double AdxThreshold = 25.0;
input int AtrPeriod = 14;
input double AtrMultSL = 1.2;
input double AtrMultTrail = 1.5;
input double AtrTrailStep = 0.5;
input double AtrVolFilterMult = 1.8;
input int MaxSpreadPoints = 20;
input int Slippage = 10;
//--- Indicator handles
int g_hFastEmaH1=INVALID_HANDLE, g_hSlowEmaH1=INVALID_HANDLE;
int g_hTrendEmaH4=INVALID_HANDLE, g_hAdxH1=INVALID_HANDLE, g_hAtrH1=INVALID_HANDLE;
//--- Tracking variables
datetime g_lastBarTime=0;
double g_dayStartEquity=0.0, g_peakEquity=0.0;
bool g_tradingHalted=false, g_totalHalted=false;
int g_dayOfYear=-1;
//+------------------------------------------------------------------+
int OnInit()
{
if(MagicNumber<0 || DailyLossLimitPct<=0 || DailyLossLimitPct>=100 || TotalDDLimitPct<=0 || TotalDDLimitPct>=100 ||
FridayCloseHour<0 || FridayCloseHour>23 || RiskPerTradePct<=0 || FastEmaPeriod<1 || SlowEmaPeriod<1 ||
TrendEmaPeriod<1 || AdxPeriod<1 || AtrPeriod<1 || AtrMultSL<=0 || AtrMultTrail<=0 || AtrTrailStep<=0 ||
AtrVolFilterMult<=0 || MaxSpreadPoints<0 || Slippage<0) return INIT_PARAMETERS_INCORRECT;
Trade.SetExpertMagicNumber(MagicNumber);
Trade.SetDeviationInPoints(Slippage);
if(!Trade.SetTypeFillingBySymbol(_Symbol)) return INIT_FAILED;
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);
if(g_hFastEmaH1==INVALID_HANDLE||g_hSlowEmaH1==INVALID_HANDLE||
g_hTrendEmaH4==INVALID_HANDLE||g_hAdxH1==INVALID_HANDLE||
g_hAtrH1==INVALID_HANDLE)
{ Print("FTMO EA ERROR: Failed to create indicator handles."); return INIT_FAILED; }
g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
g_peakEquity = g_dayStartEquity;
if(g_dayStartEquity<=0) return INIT_FAILED;
Print("FTMO_CompatibleEA initialized on ", _Symbol);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
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);
}
//+------------------------------------------------------------------+
double GetBuffer(int handle, int bufIdx, int shift)
{
double buf[]; ArraySetAsSeries(buf,true);
if(CopyBuffer(handle,bufIdx,shift,1,buf)<1) return EMPTY_VALUE;
return buf[0];
}
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime curBar=iTime(_Symbol,PERIOD_H1,0);
if(curBar==0||curBar==g_lastBarTime) return false;
g_lastBarTime=curBar; return true;
}
//+------------------------------------------------------------------+
int CountPositions(int filter=-1)
{
int count=0;
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong t=PositionGetTicket(i);
if(!PositionSelectByTicket(t)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
if(filter>=0&&(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)!=filter) continue;
count++;
}
return count;
}
//+------------------------------------------------------------------+
void CloseAll()
{
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong t=PositionGetTicket(i);
if(!PositionSelectByTicket(t)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
if(!Trade.PositionClose(t,Slippage) || (Trade.ResultRetcode()!=TRADE_RETCODE_DONE && Trade.ResultRetcode()!=TRADE_RETCODE_DONE_PARTIAL))
Print("Close failed: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
bool CheckDayReset()
{
MqlDateTime dt;
if(!TimeToStruct(TimeCurrent(),dt)) return false;
int key=dt.year*400+dt.day_of_year;
if(key!=g_dayOfYear)
{
g_dayOfYear=key;
g_dayStartEquity=AccountInfoDouble(ACCOUNT_EQUITY);
g_tradingHalted=false;
}
return g_dayStartEquity>0;
}
bool IsDailyLossHit()
{
double eq=AccountInfoDouble(ACCOUNT_EQUITY);
if(g_dayStartEquity<=0) return true;
if((g_dayStartEquity-eq)/g_dayStartEquity*100.0>=DailyLossLimitPct) g_tradingHalted=true;
return g_tradingHalted;
}
bool IsMaxDDHit()
{
double eq=AccountInfoDouble(ACCOUNT_EQUITY);
if(eq>g_peakEquity) g_peakEquity=eq;
if(g_peakEquity<=0) return true;
if((g_peakEquity-eq)/g_peakEquity*100.0>=TotalDDLimitPct) g_totalHalted=true;
return g_totalHalted;
}
//+------------------------------------------------------------------+
bool IsWeekendClose()
{
if(!CloseOnFriday) return false;
MqlDateTime dt; if(!TimeToStruct(TimeCurrent(),dt)) return true;
return(dt.day_of_week==0 || dt.day_of_week==6 || (dt.day_of_week==5&&dt.hour>=FridayCloseHour));
}
//+------------------------------------------------------------------+
bool SpreadOk()
{
MqlTick t; if(!SymbolInfoTick(_Symbol,t)) return false;
return((t.ask-t.bid)/SymbolInfoDouble(_Symbol,SYMBOL_POINT)<=MaxSpreadPoints);
}
//+------------------------------------------------------------------+
double CalcLots(double slPoints)
{
double riskAmt=AccountInfoDouble(ACCOUNT_BALANCE)*RiskPerTradePct/100.0;
double tv=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
double ts=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(slPoints<=0||ts<=0) return 0;
double slM=(slPoints/ts)*tv;
if(slM<=0) return 0;
double s=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double minLot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN), maxLot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
if(s<=0 || minLot<=0 || maxLot<minLot || riskAmt<=0 || tv<=0) return 0;
double lots=NormalizeDouble(MathFloor(MathMin(riskAmt/slM,maxLot)/s)*s,8);
return lots>=minLot ? lots : 0;
}
//+------------------------------------------------------------------+
bool SlopingUp(){double c=GetBuffer(g_hTrendEmaH4,0,1),p=GetBuffer(g_hTrendEmaH4,0,6);return(c!=EMPTY_VALUE&&p!=EMPTY_VALUE&&c>p);}
bool SlopingDown(){double c=GetBuffer(g_hTrendEmaH4,0,1),p=GetBuffer(g_hTrendEmaH4,0,6);return(c!=EMPTY_VALUE&&p!=EMPTY_VALUE&&c<p);}
//+------------------------------------------------------------------+
void CheckedModify(ulong ticket,double sl,double tp)
{
if(!Trade.PositionModify(ticket,sl,tp) || Trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Modify failed: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
void ManageTrailing()
{
double atr=GetBuffer(g_hAtrH1,0,1),point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
if(atr==EMPTY_VALUE||atr<=0) return;
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong t=PositionGetTicket(i);
if(!PositionSelectByTicket(t)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol||(int)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
double oP=PositionGetDouble(POSITION_PRICE_OPEN),cSL=PositionGetDouble(POSITION_SL),cTP=PositionGetDouble(POSITION_TP);
double b=SymbolInfoDouble(_Symbol,SYMBOL_BID),a=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
int d=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
{
if(b>oP+AtrMultTrail*atr){double ns=NormalizeDouble(b-AtrTrailStep*atr,d);if(ns>cSL+point*2)CheckedModify(t,ns,cTP);}
}
else
{
if(a<oP-AtrMultTrail*atr){double ns=NormalizeDouble(a+AtrTrailStep*atr,d);if(ns<cSL-point*2||cSL==0)CheckedModify(t,ns,cTP);}
}
}
}
//+------------------------------------------------------------------+
void OnTick()
{
if(!CheckDayReset()) { CloseAll(); return; }
bool dailyHit=IsDailyLossHit(), totalHit=IsMaxDDHit();
if(dailyHit || totalHit) { CloseAll(); return; }
if(IsWeekendClose()){if(CountPositions()>0){CloseAll();Print("Friday close.");}return;}
ManageTrailing();
if(!IsNewBar()) return;
double fe0=GetBuffer(g_hFastEmaH1,0,1),se0=GetBuffer(g_hSlowEmaH1,0,1);
double fe1=GetBuffer(g_hFastEmaH1,0,2),se1=GetBuffer(g_hSlowEmaH1,0,2);
double adx=GetBuffer(g_hAdxH1,0,1),atr=GetBuffer(g_hAtrH1,0,1);
if(fe0==EMPTY_VALUE || se0==EMPTY_VALUE || fe1==EMPTY_VALUE || se1==EMPTY_VALUE ||
adx==EMPTY_VALUE || atr==EMPTY_VALUE || atr<=0) return;
double atr5=0;
for(int shift=2;shift<=6;shift++) {
double v=GetBuffer(g_hAtrH1,0,shift);
if(v==EMPTY_VALUE || v<=0) return;
atr5+=v;
}
atr5/=5.0;
bool bc=(fe1<=se1)&&(fe0>se0),brc=(fe1>=se1)&&(fe0<se0);
bool tu=SlopingUp(),td=SlopingDown(),ts=(adx>=AdxThreshold);
if(brc&&CountPositions(POSITION_TYPE_BUY)>0) CloseAll();
if(bc&&CountPositions(POSITION_TYPE_SELL)>0) CloseAll();
if(!SpreadOk() || atr>AtrVolFilterMult*atr5) return;
if(CountPositions()==0&&!g_tradingHalted&&!g_totalHalted)
{
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK),bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
int d=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
if(bc&&tu&&ts)
{
double sl=NormalizeDouble(ask-AtrMultSL*atr,d),tp=NormalizeDouble(ask+3.0*atr,d),lots=CalcLots(ask-sl);
if(lots>0 && (!Trade.Buy(lots,_Symbol,ask,sl,tp,"Risk example buy") ||
(Trade.ResultRetcode()!=TRADE_RETCODE_DONE && Trade.ResultRetcode()!=TRADE_RETCODE_DONE_PARTIAL)))
Print("Buy failed: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
if(brc&&td&&ts)
{
double sl=NormalizeDouble(bid+AtrMultSL*atr,d),tp=NormalizeDouble(bid-3.0*atr,d),lots=CalcLots(sl-bid);
if(lots>0 && (!Trade.Sell(lots,_Symbol,bid,sl,tp,"Risk example sell") ||
(Trade.ResultRetcode()!=TRADE_RETCODE_DONE && Trade.ResultRetcode()!=TRADE_RETCODE_DONE_PARTIAL)))
Print("Sell failed: ",Trade.ResultRetcode()," ",Trade.ResultRetcodeDescription());
}
}
}
//+------------------------------------------------------------------+Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom Multi-pair prop-firm 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.