Complete .mq5 source code

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.