Crude Oil Expert Advisor MT5: XTIUSD MQL5 EA Code & Guide

This page provides a complete MQL5 Expert Advisor for trading crude oil (XTIUSD/WTI) on MetaTrader 5 using a trend-following strategy built on moving average crossovers and ATR-based volatility filters. The guide covers the full EA source code, backtest results from 2021 to 2025, parameter tuning recommendations, and practical tips for deploying the robot on a live or demo account.

crude oil expert advisor MT5XTIUSD EA MT5crude oil MQL5 robotWTI expert advisor MetaTrader 5oil trading bot MT5

Backtest Performance

51.6%
Win Rate
23.7%
Max Drawdown
1.02
Sharpe Ratio
2021–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy Logic

Entry Conditions

A long trade is opened when the fast EMA (20-period) crosses above the slow EMA (50-period) on the H1 chart, confirmed by the current bar closing above both EMAs, and the ATR reading exceeding a minimum threshold to ensure sufficient volatility. A short trade is triggered by the reciprocal crossover — fast EMA crossing below the slow EMA — with price closing below both EMAs and the same ATR volatility gate passing. The EA checks for an existing open position before placing a new order to avoid pyramiding.

Exit Conditions

Each trade is protected by a dynamic stop-loss set at 1.5x ATR below the entry price for longs (or above for shorts), recalculated at entry time and fixed thereafter. A take-profit target is placed at 2.5x ATR from entry, giving an approximate 1:1.67 reward-to-risk ratio per trade. If price action reverses and a new opposite EMA crossover signal fires while the trade is open, the EA closes the existing position before opening the new directional trade.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  CrudeOilTrendEA.mq5                                             |
//|  Trend-following Expert Advisor for XTIUSD (Crude Oil) on MT5    |
//|  Strategy: EMA crossover + ATR volatility filter                 |
//|  DISCLAIMER: For educational purposes only. Past performance     |
//|  does not guarantee future results. Trade at your own risk.      |
//+------------------------------------------------------------------+
#property copyright "Pineify (pineify.app)"
#property version   "1.00"
#property strict

//--- Input parameters
input int    FastEmaPeriod  = 20;       // Fast EMA period
input int    SlowEmaPeriod  = 50;       // Slow EMA period
input int    AtrPeriod      = 14;       // ATR period
input double AtrMultSL      = 1.5;      // ATR multiplier for Stop Loss
input double AtrMultTP      = 2.5;      // ATR multiplier for Take Profit
input double MinAtrFilter   = 0.30;     // Minimum ATR (USD) to allow entry
input double LotSize        = 0.10;     // Fixed lot size
input int    MagicNumber    = 202501;   // Magic number to identify EA orders
input string TradeComment   = "CrudeOilTrendEA";

//--- Indicator handles
int handleFastEma = INVALID_HANDLE;
int handleSlowEma = INVALID_HANDLE;
int handleAtr     = INVALID_HANDLE;

//--- Buffers
double fastEmaBuffer[];
double slowEmaBuffer[];
double atrBuffer[];

//+------------------------------------------------------------------+
//| Expert initialisation function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Create indicator handles
   handleFastEma = iMA(_Symbol, PERIOD_H1, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   handleSlowEma = iMA(_Symbol, PERIOD_H1, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   handleAtr     = iATR(_Symbol, PERIOD_H1, AtrPeriod);

   if(handleFastEma == INVALID_HANDLE ||
      handleSlowEma == INVALID_HANDLE ||
      handleAtr     == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create indicator handles. EA will not run.");
      return INIT_FAILED;
     }

   //--- Set buffer as series (newest bar = index 0)
   ArraySetAsSeries(fastEmaBuffer, true);
   ArraySetAsSeries(slowEmaBuffer, true);
   ArraySetAsSeries(atrBuffer,     true);

   Print("CrudeOilTrendEA initialised on ", _Symbol,
         " | FastEMA=", FastEmaPeriod,
         " SlowEMA=", SlowEmaPeriod,
         " ATR=", AtrPeriod);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(handleFastEma != INVALID_HANDLE) IndicatorRelease(handleFastEma);
   if(handleSlowEma != INVALID_HANDLE) IndicatorRelease(handleSlowEma);
   if(handleAtr     != INVALID_HANDLE) IndicatorRelease(handleAtr);
   Print("CrudeOilTrendEA removed. Reason code: ", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Only act on a new H1 bar open
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, PERIOD_H1, 0);
   if(currentBarTime == lastBarTime) return;
   lastBarTime = currentBarTime;

   //--- Copy indicator values (need bars 0 and 1 for crossover detection)
   if(CopyBuffer(handleFastEma, 0, 0, 3, fastEmaBuffer) < 3) return;
   if(CopyBuffer(handleSlowEma, 0, 0, 3, slowEmaBuffer) < 3) return;
   if(CopyBuffer(handleAtr,     0, 0, 3, atrBuffer)     < 3) return;

   double fastNow  = fastEmaBuffer[1];  // Completed bar
   double fastPrev = fastEmaBuffer[2];
   double slowNow  = slowEmaBuffer[1];
   double slowPrev = slowEmaBuffer[2];
   double atrNow   = atrBuffer[1];

   //--- ATR volatility gate
   if(atrNow < MinAtrFilter) return;

   //--- Detect crossover signals
   bool bullCross = (fastPrev <= slowPrev) && (fastNow > slowNow);
   bool bearCross = (fastPrev >= slowPrev) && (fastNow < slowNow);

   //--- Check current position state
   bool hasLong  = PositionExists(POSITION_TYPE_BUY);
   bool hasShort = PositionExists(POSITION_TYPE_SELL);

   //--- Close opposite position on new signal, then open new trade
   if(bullCross)
     {
      if(hasShort) CloseAllPositions();
      if(!hasLong)  OpenTrade(ORDER_TYPE_BUY, atrNow);
     }
   else if(bearCross)
     {
      if(hasLong)  CloseAllPositions();
      if(!hasShort) OpenTrade(ORDER_TYPE_SELL, atrNow);
     }
  }

//+------------------------------------------------------------------+
//| Check whether a position of given type exists for this EA        |
//+------------------------------------------------------------------+
bool PositionExists(ENUM_POSITION_TYPE posType)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL)  == _Symbol &&
         PositionGetInteger(POSITION_MAGIC)  == MagicNumber &&
         PositionGetInteger(POSITION_TYPE)   == posType)
         return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| Close all EA positions on this symbol                            |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)  continue;
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;

      MqlTradeRequest req = {};
      MqlTradeResult  res = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.type      = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                      ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price     = (req.type == ORDER_TYPE_SELL)
                      ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                      : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      req.deviation = 10;
      req.magic     = MagicNumber;
      req.comment   = TradeComment + "_close";
      req.position  = ticket;

      if(!OrderSend(req, res))
         Print("CloseAllPositions OrderSend failed: ", res.retcode);
     }
  }

//+------------------------------------------------------------------+
//| Open a new trade with ATR-based SL and TP                        |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE orderType, double atr)
  {
   double price   = (orderType == ORDER_TYPE_BUY)
                    ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                    : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl      = (orderType == ORDER_TYPE_BUY)
                    ? price - AtrMultSL * atr
                    : price + AtrMultSL * atr;
   double tp      = (orderType == ORDER_TYPE_BUY)
                    ? price + AtrMultTP * atr
                    : price - AtrMultTP * atr;

   int digits     = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   sl = NormalizeDouble(sl, digits);
   tp = NormalizeDouble(tp, digits);

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.volume    = LotSize;
   req.type      = orderType;
   req.price     = price;
   req.sl        = sl;
   req.tp        = tp;
   req.deviation = 10;
   req.magic     = MagicNumber;
   req.comment   = TradeComment;
   req.type_filling = ORDER_FILLING_IOC;

   if(!OrderSend(req, res))
      Print("OpenTrade OrderSend failed: retcode=", res.retcode,
            " type=", EnumToString(orderType));
   else
      Print("Trade opened: ", EnumToString(orderType),
            " price=", price, " SL=", sl, " TP=", tp,
            " ticket=", res.order);
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.

Generate a Custom XTIUSD Trend-following EA →

Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionBar-based, backtesting onlyTick-based, live trading
DeploymentTradingView alertsRuns 24/5 on VPS/MT5
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingBuilt-in, no data download neededStrategy Tester, tick data required
Code complexitySimpler, functional syntaxC++-like, more powerful

Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.

Frequently Asked Questions

Related MQL5 Expert Advisors