🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Hey everyone,
While we spend a lot of time analyzing forex and silver pairs here on forex-scalping.com, I wanted to pivot and share a robust approach for scalping oil (WTI/USOIL). Oil offers incredible intraday volatility, but because of its fast-moving nature, it requires strict risk management and algorithmic discipline.
Over my 18 years in the markets, I’ve found that keeping the charts clean and focusing on price action combined with momentum filters is the most consistent way to scalp commodities.
The Strategy Setup
Instrument: USOIL / WTI / XTIUSD
Timeframe: M1 or M5
Indicators: 20 EMA, 50 EMA, and 14-period RSI
Session: London-New York overlap (peak liquidity and tightest spreads)
The Entry Rules
Long Setup:
Trend Alignment: Wait for the 20 EMA to cleanly cross above the 50 EMA.
Momentum Filter: The 14-period RSI must be above 50, confirming buying pressure.
Execution: Enter at market on the close of the crossover candle.
Short Setup:
Trend Alignment: Wait for the 20 EMA to cross below the 50 EMA.
Momentum Filter: The 14-period RSI must be below 50.
Execution: Enter at market on the close of the crossover candle.
While we spend a lot of time analyzing forex and silver pairs here on forex-scalping.com, I wanted to pivot and share a robust approach for scalping oil (WTI/USOIL). Oil offers incredible intraday volatility, but because of its fast-moving nature, it requires strict risk management and algorithmic discipline.
Over my 18 years in the markets, I’ve found that keeping the charts clean and focusing on price action combined with momentum filters is the most consistent way to scalp commodities.
The Strategy Setup
Instrument: USOIL / WTI / XTIUSD
Timeframe: M1 or M5
Indicators: 20 EMA, 50 EMA, and 14-period RSI
Session: London-New York overlap (peak liquidity and tightest spreads)
The Entry Rules
Long Setup:
Trend Alignment: Wait for the 20 EMA to cleanly cross above the 50 EMA.
Momentum Filter: The 14-period RSI must be above 50, confirming buying pressure.
Execution: Enter at market on the close of the crossover candle.
Short Setup:
Trend Alignment: Wait for the 20 EMA to cross below the 50 EMA.
Momentum Filter: The 14-period RSI must be below 50.
Execution: Enter at market on the close of the crossover candle.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Risk Management
For scalping oil, stop losses need to be tight but respect the asset's noise. I typically aim for a strict 1:1.5 or 1:2 Risk-to-Reward ratio. Because oil spreads can fluctuate, ensure your average net profit per trade heavily outweighs the commission and slippage costs.
The MT4 Automation Script (MQL4)
To take the emotion out of the execution, I've written a lightweight MT4 Expert Advisor that automatically trades this EMA/RSI crossover logic. You can compile this in MetaEditor and run it directly on your oil charts.
For scalping oil, stop losses need to be tight but respect the asset's noise. I typically aim for a strict 1:1.5 or 1:2 Risk-to-Reward ratio. Because oil spreads can fluctuate, ensure your average net profit per trade heavily outweighs the commission and slippage costs.
The MT4 Automation Script (MQL4)
To take the emotion out of the execution, I've written a lightweight MT4 Expert Advisor that automatically trades this EMA/RSI crossover logic. You can compile this in MetaEditor and run it directly on your oil charts.
Code: Select all
//+------------------------------------------------------------------+
//| Oil_Scalper_EA.mq4 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property strict
//--- Input Parameters
input double LotSize = 0.5;
input int StopLoss = 200; // SL in points (adjust based on broker's oil decimals)
input int TakeProfit = 400; // TP in points
input int MagicNumber = 888999;
input int FastEMA = 20;
input int SlowEMA = 50;
input int RSIPeriod = 14;
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Prevent opening multiple positions at once
if(OrdersTotal() > 0) return;
// Retrieve Indicator Values
double fastEma0 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastEma1 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowEma0 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowEma1 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double rsi = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 0);
// Buy Condition: Fast EMA crosses above Slow EMA and RSI > 50
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && rsi > 50)
{
double sl = Ask - StopLoss * Point;
double tp = Ask + TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, sl, tp, "Oil Scalp Buy", MagicNumber, 0, clrBlue);
}
// Sell Condition: Fast EMA crosses below Slow EMA and RSI < 50
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && rsi < 50)
{
double sl = Bid + StopLoss * Point;
double tp = Bid - TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, sl, tp, "Oil Scalp Sell", MagicNumber, 0, clrRed);
}
}
//+------------------------------------------------------------------+Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
To add a time filter, you can introduce input variables for the start and end hours and use the built-in Hour() function to check the current broker server time before executing any trades.
The London and New York overlap typically runs from 13:00 to 17:00 GMT, but because MT4 runs on your broker's server time (not your local time in the Czech Republic), you need to make these hours adjustable so you can align them with your specific broker's timezone offset.
Here is the updated script with the time filter integrated:
What Changed:
Added Time Inputs: TradeStartHour and TradeEndHour allow you (or your forum members) to quickly tweak the active window directly from the EA properties box without recompiling the code.
The Session Check: The Hour() function grabs the current server hour (e.g., if it's 14:35, Hour() returns 14). The script verifies that the current hour is >= TradeStartHour and < TradeEndHour.
The Early Exit: If isTradingTime returns false, the script hits return; and stops reading the rest of the logic until the next tick, saving CPU resources when the markets are quieter.
The London and New York overlap typically runs from 13:00 to 17:00 GMT, but because MT4 runs on your broker's server time (not your local time in the Czech Republic), you need to make these hours adjustable so you can align them with your specific broker's timezone offset.
Here is the updated script with the time filter integrated:
Code: Select all
//+------------------------------------------------------------------+
//| Oil_Scalper_EA.mq4 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property strict
//--- Input Parameters
input double LotSize = 0.5;
input int StopLoss = 200; // SL in points
input int TakeProfit = 400; // TP in points
input int MagicNumber = 888999;
input int FastEMA = 20;
input int SlowEMA = 50;
input int RSIPeriod = 14;
//--- Session Time Filter
input int TradeStartHour = 14; // Broker server hour for NY Open (Adjust to your broker)
input int TradeEndHour = 17; // Broker server hour for London Close (Adjust to your broker)
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if we are inside the allowed trading session
int currentHour = Hour();
bool isTradingTime = (currentHour >= TradeStartHour && currentHour < TradeEndHour);
// If outside the overlap hours, exit the function and do not trade
if(!isTradingTime) return;
// 2. Prevent opening multiple positions at once
if(OrdersTotal() > 0) return;
// 3. Retrieve Indicator Values
double fastEma0 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastEma1 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowEma0 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowEma1 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double rsi = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 0);
// Buy Condition: Fast EMA crosses above Slow EMA and RSI > 50
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && rsi > 50)
{
double sl = Ask - StopLoss * Point;
double tp = Ask + TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, sl, tp, "Oil Scalp Buy", MagicNumber, 0, clrBlue);
}
// Sell Condition: Fast EMA crosses below Slow EMA and RSI < 50
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && rsi < 50)
{
double sl = Bid + StopLoss * Point;
double tp = Bid - TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, sl, tp, "Oil Scalp Sell", MagicNumber, 0, clrRed);
}
}
//+------------------------------------------------------------------+Added Time Inputs: TradeStartHour and TradeEndHour allow you (or your forum members) to quickly tweak the active window directly from the EA properties box without recompiling the code.
The Session Check: The Hour() function grabs the current server hour (e.g., if it's 14:35, Hour() returns 14). The script verifies that the current hour is >= TradeStartHour and < TradeEndHour.
The Early Exit: If isTradingTime returns false, the script hits return; and stops reading the rest of the logic until the next tick, saving CPU resources when the markets are quieter.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
To capture sudden momentum spikes while protecting your floating profits, you can implement a step-based trailing stop.
A standard trailing stop updates with every single tick, which can spam the broker's server and lead to execution delays. A step-based approach uses a TrailingStep variable, ensuring the stop loss only modifies after the price has moved a predefined distance in your favor.
Here is the updated code incorporating a custom ManageTrailingStop() function:
Key Additions:
The For Loop: The function scans backward (OrdersTotal() - 1) through the open trades. This is the safest way to iterate through orders in MQL4 without skipping indexes if an order closes mid-loop.
Symbol and Magic Number Lock: OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber ensures the EA only manages its own oil trades, completely ignoring manual positions or trades executed by other scripts.
Step Logic Validation: The script actively measures the distance between the current price and the existing Stop Loss. It only triggers OrderModify() if the required TrailingStep threshold has been breached.
A standard trailing stop updates with every single tick, which can spam the broker's server and lead to execution delays. A step-based approach uses a TrailingStep variable, ensuring the stop loss only modifies after the price has moved a predefined distance in your favor.
Here is the updated code incorporating a custom ManageTrailingStop() function:
Code: Select all
//+------------------------------------------------------------------+
//| Oil_Scalper_EA.mq4 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property strict
//--- Input Parameters
input double LotSize = 0.5;
input int StopLoss = 200;
input int TakeProfit = 400;
input int MagicNumber = 888999;
input int FastEMA = 20;
input int SlowEMA = 50;
input int RSIPeriod = 14;
//--- Session Time Filter
input int TradeStartHour = 14;
input int TradeEndHour = 17;
//--- Trailing Stop Settings
input int TrailingStop = 150; // Distance to trail the price (in points)
input int TrailingStep = 50; // Minimum move before updating the SL (in points)
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage open positions first (Trailing Stop)
ManageTrailingStop();
// 2. Check time filter
int currentHour = Hour();
bool isTradingTime = (currentHour >= TradeStartHour && currentHour < TradeEndHour);
if(!isTradingTime) return;
// 3. Prevent multiple positions
if(OrdersTotal() > 0) return;
// 4. Retrieve Indicator Values
double fastEma0 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastEma1 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowEma0 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowEma1 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double rsi = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 0);
// Buy Condition
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && rsi > 50)
{
double sl = Ask - StopLoss * Point;
double tp = Ask + TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, sl, tp, "Oil Scalp Buy", MagicNumber, 0, clrBlue);
}
// Sell Condition
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && rsi < 50)
{
double sl = Bid + StopLoss * Point;
double tp = Bid - TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, sl, tp, "Oil Scalp Sell", MagicNumber, 0, clrRed);
}
}
//+------------------------------------------------------------------+
//| Manage Trailing Stop Function |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
// Buy Order Trailing Logic
if(OrderType() == OP_BUY)
{
if(Bid - OrderOpenPrice() > TrailingStop * Point)
{
if(OrderStopLoss() < Bid - (TrailingStop + TrailingStep) * Point || OrderStopLoss() == 0)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), Bid - TrailingStop * Point, OrderTakeProfit(), 0, clrBlue);
}
}
}
// Sell Order Trailing Logic
else if(OrderType() == OP_SELL)
{
if(OrderOpenPrice() - Ask > TrailingStop * Point)
{
if(OrderStopLoss() > Ask + (TrailingStop + TrailingStep) * Point || OrderStopLoss() == 0)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), Ask + TrailingStop * Point, OrderTakeProfit(), 0, clrRed);
}
}
}
}
}
}
}
//+------------------------------------------------------------------+The For Loop: The function scans backward (OrdersTotal() - 1) through the open trades. This is the safest way to iterate through orders in MQL4 without skipping indexes if an order closes mid-loop.
Symbol and Magic Number Lock: OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber ensures the EA only manages its own oil trades, completely ignoring manual positions or trades executed by other scripts.
Step Logic Validation: The script actively measures the distance between the current price and the existing Stop Loss. It only triggers OrderModify() if the required TrailingStep threshold has been breached.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
To protect your entries from sudden liquidity drops and spread widening—which is especially important for scalping commodities—you can query the broker's current spread in real-time and block new trades if it exceeds your threshold.
The key is to place this check after the ManageTrailingStop() function. You want the EA to continue managing your open trades and locking in profits even if the spread is currently too wide to open a new one.
Here is the updated script with the maximum spread filter included:
The key is to place this check after the ManageTrailingStop() function. You want the EA to continue managing your open trades and locking in profits even if the spread is currently too wide to open a new one.
Here is the updated script with the maximum spread filter included:
Code: Select all
//+------------------------------------------------------------------+
//| Oil_Scalper_EA.mq4 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property strict
//--- Input Parameters
input double LotSize = 0.5;
input int StopLoss = 200;
input int TakeProfit = 400;
input int MagicNumber = 888999;
input int FastEMA = 20;
input int SlowEMA = 50;
input int RSIPeriod = 14;
//--- Session Time Filter
input int TradeStartHour = 14;
input int TradeEndHour = 17;
//--- Trailing Stop Settings
input int TrailingStop = 150;
input int TrailingStep = 50;
//--- Spread Filter
input int MaxSpread = 40; // Maximum allowed spread in points
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage open positions first (Trailing Stop must always run)
ManageTrailingStop();
// 2. Check time filter
int currentHour = Hour();
bool isTradingTime = (currentHour >= TradeStartHour && currentHour < TradeEndHour);
if(!isTradingTime) return;
// 3. Prevent multiple positions
if(OrdersTotal() > 0) return;
// 4. Check Spread Filter
// MarketInfo returns the current spread in points
int currentSpread = (int)MarketInfo(Symbol(), MODE_SPREAD);
// If the spread is wider than allowed, exit before reading indicators
if(currentSpread > MaxSpread) return;
// 5. Retrieve Indicator Values
double fastEma0 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastEma1 = iMA(NULL, 0, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowEma0 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowEma1 = iMA(NULL, 0, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double rsi = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 0);
// Buy Condition
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && rsi > 50)
{
double sl = Ask - StopLoss * Point;
double tp = Ask + TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, sl, tp, "Oil Scalp Buy", MagicNumber, 0, clrBlue);
}
// Sell Condition
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && rsi < 50)
{
double sl = Bid + StopLoss * Point;
double tp = Bid - TakeProfit * Point;
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, sl, tp, "Oil Scalp Sell", MagicNumber, 0, clrRed);
}
}
//+------------------------------------------------------------------+
//| Manage Trailing Stop Function |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
// Buy Order Trailing Logic
if(OrderType() == OP_BUY)
{
if(Bid - OrderOpenPrice() > TrailingStop * Point)
{
if(OrderStopLoss() < Bid - (TrailingStop + TrailingStep) * Point || OrderStopLoss() == 0)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), Bid - TrailingStop * Point, OrderTakeProfit(), 0, clrBlue);
}
}
}
// Sell Order Trailing Logic
else if(OrderType() == OP_SELL)
{
if(OrderOpenPrice() - Ask > TrailingStop * Point)
{
if(OrderStopLoss() > Ask + (TrailingStop + TrailingStep) * Point || OrderStopLoss() == 0)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), Ask + TrailingStop * Point, OrderTakeProfit(), 0, clrRed);
}
}
}
}
}
}
}
//+------------------------------------------------------------------+Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Key Additions:
MaxSpread Input: You can adjust the tolerance dynamically from the EA properties window without recompiling.
MarketInfo(Symbol(), MODE_SPREAD): This native MT4 function pulls the exact, real-time spread from the broker in points.
Early Exit Sequence: By placing if(currentSpread > MaxSpread) return; immediately after the time filter and open trades check, the EA saves processing power by not calculating the EMA and RSI values when the market conditions aren't safe for a scalp.
MaxSpread Input: You can adjust the tolerance dynamically from the EA properties window without recompiling.
MarketInfo(Symbol(), MODE_SPREAD): This native MT4 function pulls the exact, real-time spread from the broker in points.
Early Exit Sequence: By placing if(currentSpread > MaxSpread) return; immediately after the time filter and open trades check, the EA saves processing power by not calculating the EMA and RSI values when the market conditions aren't safe for a scalp.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Moving from MT4 to MT5 requires a complete rewrite because MQL5 handles indicators and trade execution very differently.
In MT5, you cannot simply call an indicator function like iMA() inside OnTick(). Instead, you have to create an "indicator handle" when the EA loads, and then pull data into an array (buffer) on every tick. Additionally, MT5 uses a "Position" system rather than an "Order" system for open trades.
To make execution clean and reliable, we use the built-in <Trade\Trade.mqh> standard library.
Here is the fully converted, MT5-ready version of the WTI Scalping strategy:
Important MT5 Architecture Changes:
CTrade Class: Replaces the clunky OrderSend() function with trade.Buy(), trade.Sell(), and trade.PositionModify(). It handles the complex trade requests in the background.
Handles & Arrays: Indicators now generate a handle in OnInit(). During OnTick(), we copy the exact number of candles we need into an array (fastEmaArray), using ArraySetAsSeries() to make sure index 0 is always the current live candle—mirroring how MT4 works.
Positions, Not Orders: We built a custom PositionsTotalThisEA() function. MT5 distinguishes between active trades ("Positions") and pending limits/stops ("Orders").
In MT5, you cannot simply call an indicator function like iMA() inside OnTick(). Instead, you have to create an "indicator handle" when the EA loads, and then pull data into an array (buffer) on every tick. Additionally, MT5 uses a "Position" system rather than an "Order" system for open trades.
To make execution clean and reliable, we use the built-in <Trade\Trade.mqh> standard library.
Here is the fully converted, MT5-ready version of the WTI Scalping strategy:
Code: Select all
//+------------------------------------------------------------------+
//| Oil_Scalper_EA.mq5 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property version "1.00"
#include <Trade\Trade.mqh> // Include standard trade library
CTrade trade; // Initialize the CTrade object
//--- Input Parameters
input double LotSize = 0.5;
input int StopLoss = 200;
input int TakeProfit = 400;
input ulong MagicNumber = 888999; // Note: MagicNumber is ulong in MT5
input int FastEMA = 20;
input int SlowEMA = 50;
input int RSIPeriod = 14;
//--- Session Time Filter
input int TradeStartHour = 14;
input int TradeEndHour = 17;
//--- Trailing Stop Settings
input int TrailingStop = 150;
input int TrailingStep = 50;
//--- Spread Filter
input int MaxSpread = 40;
//--- Indicator Handles
int handleFastEMA;
int handleSlowEMA;
int handleRSI;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Set the magic number for the CTrade object
trade.SetExpertMagicNumber(MagicNumber);
// Initialize indicator handles
handleFastEMA = iMA(_Symbol, PERIOD_CURRENT, FastEMA, 0, MODE_EMA, PRICE_CLOSE);
handleSlowEMA = iMA(_Symbol, PERIOD_CURRENT, SlowEMA, 0, MODE_EMA, PRICE_CLOSE);
handleRSI = iRSI(_Symbol, PERIOD_CURRENT, RSIPeriod, PRICE_CLOSE);
// Check if handles were created successfully
if(handleFastEMA == INVALID_HANDLE || handleSlowEMA == INVALID_HANDLE || handleRSI == INVALID_HANDLE)
{
Print("Error: Failed to initialize indicators.");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Free up memory by releasing indicator handles
IndicatorRelease(handleFastEMA);
IndicatorRelease(handleSlowEMA);
IndicatorRelease(handleRSI);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage open positions (Trailing Stop)
ManageTrailingStop();
// 2. Check time filter
MqlDateTime timeStruct;
TimeCurrent(timeStruct);
bool isTradingTime = (timeStruct.hour >= TradeStartHour && timeStruct.hour < TradeEndHour);
if(!isTradingTime) return;
// 3. Prevent multiple positions (Count only this EA's positions)
if(PositionsTotalThisEA() > 0) return;
// 4. Check Spread Filter
long currentSpread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(currentSpread > MaxSpread) return;
// 5. Retrieve Indicator Values via CopyBuffer
double fastEmaArray[], slowEmaArray[], rsiArray[];
// Make arrays behave like MT4 (index 0 is current unclosed candle)
ArraySetAsSeries(fastEmaArray, true);
ArraySetAsSeries(slowEmaArray, true);
ArraySetAsSeries(rsiArray, true);
// Copy the last 2 candles for EMA, and 1 for RSI
if(CopyBuffer(handleFastEMA, 0, 0, 2, fastEmaArray) <= 0) return;
if(CopyBuffer(handleSlowEMA, 0, 0, 2, slowEmaArray) <= 0) return;
if(CopyBuffer(handleRSI, 0, 0, 1, rsiArray) <= 0) return;
double fastEma0 = fastEmaArray[0];
double fastEma1 = fastEmaArray[1];
double slowEma0 = slowEmaArray[0];
double slowEma1 = slowEmaArray[1];
double currentRsi = rsiArray[0];
// Fetch current Bid/Ask and Point size for MT5
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
// Buy Condition
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && currentRsi > 50)
{
double sl = ask - StopLoss * point;
double tp = ask + TakeProfit * point;
trade.Buy(LotSize, _Symbol, ask, sl, tp, "Oil Scalp Buy");
}
// Sell Condition
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && currentRsi < 50)
{
double sl = bid + StopLoss * point;
double tp = bid - TakeProfit * point;
trade.Sell(LotSize, _Symbol, bid, sl, tp, "Oil Scalp Sell");
}
}
//+------------------------------------------------------------------+
//| Custom Function: Count Positions for this EA |
//+------------------------------------------------------------------+
int PositionsTotalThisEA()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| Manage Trailing Stop Function |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i); // Selects position and gets ticket
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
long posType = PositionGetInteger(POSITION_TYPE);
// Buy Position Trailing Logic
if(posType == POSITION_TYPE_BUY)
{
if(bid - openPrice > TrailingStop * point)
{
double newSL = bid - TrailingStop * point;
if(currentSL < bid - (TrailingStop + TrailingStep) * point || currentSL == 0)
{
trade.PositionModify(ticket, newSL, currentTP);
}
}
}
// Sell Position Trailing Logic
else if(posType == POSITION_TYPE_SELL)
{
if(openPrice - ask > TrailingStop * point)
{
double newSL = ask + TrailingStop * point;
if(currentSL > ask + (TrailingStop + TrailingStep) * point || currentSL == 0)
{
trade.PositionModify(ticket, newSL, currentTP);
}
}
}
}
}
}
//+------------------------------------------------------------------+CTrade Class: Replaces the clunky OrderSend() function with trade.Buy(), trade.Sell(), and trade.PositionModify(). It handles the complex trade requests in the background.
Handles & Arrays: Indicators now generate a handle in OnInit(). During OnTick(), we copy the exact number of candles we need into an array (fastEmaArray), using ArraySetAsSeries() to make sure index 0 is always the current live candle—mirroring how MT4 works.
Positions, Not Orders: We built a custom PositionsTotalThisEA() function. MT5 distinguishes between active trades ("Positions") and pending limits/stops ("Orders").
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
To calculate your position size dynamically based on a percentage of your account equity, we need to divide your maximum monetary risk by the financial cost of a 1-lot position hitting your specific Stop Loss.
Because oil (WTI) and other commodities often have non-standard contract sizes and tick values compared to forex, the calculation must query the broker's specific SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE, and SYMBOL_VOLUME_STEP to avoid "Invalid Volume" order rejections.
Here is how to update your MQL5 script.
Add Risk Management Inputs
Replace your single LotSize input at the top of the file with these variables to allow toggling between auto and fixed lot sizing:
Because oil (WTI) and other commodities often have non-standard contract sizes and tick values compared to forex, the calculation must query the broker's specific SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE, and SYMBOL_VOLUME_STEP to avoid "Invalid Volume" order rejections.
Here is how to update your MQL5 script.
Add Risk Management Inputs
Replace your single LotSize input at the top of the file with these variables to allow toggling between auto and fixed lot sizing:
Code: Select all
//--- Risk Management Inputs
input bool UseAutoLot = true; // Enable dynamic lot sizing
input double RiskPercent = 1.0; // Risk per trade (% of Equity)
input double FixedLotSize = 0.5; // Fallback fixed lot size
input int StopLoss = 200; // SL in points
input int TakeProfit = 400;
input ulong MagicNumber = 888999;Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Add the Lot Sizing Function
Place this custom function anywhere at the bottom of your script (outside of OnTick()). It handles the equity math and rounds the final volume to match your broker's allowed increments:
Place this custom function anywhere at the bottom of your script (outside of OnTick()). It handles the equity math and rounds the final volume to match your broker's allowed increments:
Code: Select all
//+------------------------------------------------------------------+
//| Custom Function: Calculate Lot Size based on Equity Risk |
//+------------------------------------------------------------------+
double CalculateLotSize()
{
// Return fixed lot if auto-lot is disabled
if(!UseAutoLot) return FixedLotSize;
// 1. Calculate risk amount in account currency
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskAmount = equity * (RiskPercent / 100.0);
// 2. Fetch symbol contract specifications
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
// 3. Calculate monetary loss for 1 standard lot hitting the Stop Loss
double lossPerLot = (StopLoss * point) / tickSize * tickValue;
// Prevent division by zero if broker data is missing
if(lossPerLot <= 0) return FixedLotSize;
// 4. Calculate raw lot size
double calculatedLot = riskAmount / lossPerLot;
// 5. Normalize lot size to broker limits and step increments
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
// Round to the nearest allowed step
calculatedLot = MathRound(calculatedLot / stepLot) * stepLot;
// Clamp to min/max limits
if(calculatedLot < minLot) calculatedLot = minLot;
if(calculatedLot > maxLot) calculatedLot = maxLot;
return calculatedLot;
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: 🛢️ High-Volume Oil (WTI) Scalping Strategy – 18 Years of Price Action Distilled
Update the OnTick() Execution
Inside your OnTick() function, find the execution block at the very bottom and update the variables to call CalculateLotSize() right before sending the trade:
Now, as your account equity grows, the position volume on your scalps will automatically compound, and if you hit a drawdown, the lot size will smoothly decrease to protect your remaining capital.
Inside your OnTick() function, find the execution block at the very bottom and update the variables to call CalculateLotSize() right before sending the trade:
Code: Select all
// Buy Condition
if(fastEma1 <= slowEma1 && fastEma0 > slowEma0 && currentRsi > 50)
{
double sl = ask - StopLoss * point;
double tp = ask + TakeProfit * point;
double tradeLot = CalculateLotSize(); // <-- Dynamic lot calculation
trade.Buy(tradeLot, _Symbol, ask, sl, tp, "Oil Scalp Buy");
}
// Sell Condition
if(fastEma1 >= slowEma1 && fastEma0 < slowEma0 && currentRsi < 50)
{
double sl = bid + StopLoss * point;
double tp = bid - TakeProfit * point;
double tradeLot = CalculateLotSize(); // <-- Dynamic lot calculation
trade.Sell(tradeLot, _Symbol, bid, sl, tp, "Oil Scalp Sell");
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.