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.
Backtest Performance
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
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-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.