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.
Historical test results
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, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom XTIUSD trend-following EA
Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
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.