Problem Description
Solution
To create a trading robot (Expert Advisor) that automatically adjusts its Stop Loss based on market volatility, the standard approach is to use the Average True Range (ATR) indicator.
The logic is as follows:
- Calculate Volatility: Use
iATRto get the current market range (volatility). - Determine Distance: Multiply the ATR value by a factor (e.g., 2.0) to determine a "safe" distance for the Stop Loss that allows for normal market noise.
- Adjust Stop Loss:
- For Buy orders: If price moves up, move the SL up to
Current Price - (ATR * Multiplier). Never move it down. - For Sell orders: If price moves down, move the SL down to
Current Price + (ATR * Multiplier). Never move it up.
- For Buy orders: If price moves up, move the SL up to
Below is the complete, compile-ready MQL4 code.
MQL4 Code: Volatility-Based Trailing Stop EA
//+------------------------------------------------------------------+
//| VolatilityTrailingStop.mq4 |
//| Copyright 2023, MetaQuotes |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative AI"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input string Check_Settings = "--- Strategy Settings ---";
input int MagicNumber = 123456; // Unique ID for this EA
input double LotSize = 0.1; // Fixed Lot Size
input int MAPeriod = 20; // Period for Moving Average (Entry Logic)
input string Risk_Settings = "--- Volatility SL Settings ---";
input int ATR_Period = 14; // Period for ATR calculation
input double ATR_Multiplier = 2.0; // Multiplier for SL distance (e.g., 2x ATR)
input bool TrailOnlyProfit= true; // If true, only trails once trade is in profit
//--- Global Variables
int ticket;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage Open Trades (Adjust Stop Loss)
AdjustTrailingStop();
// 2. Check for Entry Opportunities (Simple MA Logic for demonstration)
if(OrdersTotal() == 0)
{
CheckEntryLogic();
}
}
//+------------------------------------------------------------------+
//| Function to Adjust Stop Loss based on ATR |
//+------------------------------------------------------------------+
void AdjustTrailingStop()
{
// Get current volatility value
double atrValue = iATR(NULL, 0, ATR_Period, 0);
// Calculate the dynamic distance in price
double slDistance = atrValue * ATR_Multiplier;
// Get Minimum Stop Level allowed by broker to avoid Error 130
double stopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
// Filter by Symbol and Magic Number
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
// --- LOGIC FOR BUY ORDERS ---
if(OrderType() == OP_BUY)
{
// Calculate potential new Stop Loss level
double newSL = NormalizeDouble(Bid - slDistance, Digits);
// Check if we should modify:
// 1. New SL must be higher than current SL (we only tighten stops)
// 2. New SL must be far enough from current price (Broker StopLevel)
// 3. If TrailOnlyProfit is true, ensure New SL is above Open Price
bool canModify = false;
if(newSL > OrderStopLoss() && (Bid - newSL) > stopLevel)
{
if(TrailOnlyProfit)
{
if(newSL > OrderOpenPrice()) canModify = true;
}
else
{
canModify = true;
}
}
if(canModify)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrGreen))
{
Print("Error modifying Buy SL: ", GetLastError());
}
}
}
// --- LOGIC FOR SELL ORDERS ---
if(OrderType() == OP_SELL)
{
// Calculate potential new Stop Loss level
double newSL = NormalizeDouble(Ask + slDistance, Digits);
// Check if we should modify:
// 1. New SL must be lower than current SL (or current SL is 0)
// 2. New SL must be far enough from current price
bool canModify = false;
if((OrderStopLoss() == 0 || newSL < OrderStopLoss()) && (newSL - Ask) > stopLevel)
{
if(TrailOnlyProfit)
{
if(newSL < OrderOpenPrice()) canModify = true;
}
else
{
canModify = true;
}
}
if(canModify)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrRed))
{
Print("Error modifying Sell SL: ", GetLastError());
}
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Simple Entry Logic (Moving Average Crossover) |
//+------------------------------------------------------------------+
void CheckEntryLogic()
{
double maCurrent = iMA(NULL, 0, MAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
double maPrev = iMA(NULL, 0, MAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double closeCurrent = Close[0];
double closePrev = Close[1];
// Calculate Initial Stop Loss based on ATR immediately for entry
double atrValue = iATR(NULL, 0, ATR_Period, 0);
double slDist = atrValue * ATR_Multiplier;
// Buy Condition: Price crosses above MA
if(closePrev < maPrev && closeCurrent > maCurrent)
{
double initialSL = NormalizeDouble(Bid - slDist, Digits);
ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, initialSL, 0, "ATR Buy", MagicNumber, 0, clrBlue);
}
// Sell Condition: Price crosses below MA
if(closePrev > maPrev && closeCurrent < maCurrent)
{
double initialSL = NormalizeDouble(Ask + slDist, Digits);
ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, initialSL, 0, "ATR Sell", MagicNumber, 0, clrRed);
}
}
How It Works
iATRFunction: This is the core engine. It calculates the average range of price movement over the lastATR_Period(default 14) candles.AdjustTrailingStopFunction:- It runs on every tick (
OnTick). - It calculates the
slDistanceby multiplying the ATR value by yourATR_Multiplier. If the ATR is 10 pips and your multiplier is 2.0, the distance is 20 pips. - For Buys: It calculates
Bid - slDistance. If this new price is higher than the current Stop Loss, it moves the Stop Loss up. This locks in profits as the price rises. - For Sells: It calculates
Ask + slDistance. If this new price is lower than the current Stop Loss, it moves the Stop Loss down.
- It runs on every tick (
TrailOnlyProfitInput:- If set to
true, the robot will wait until the trade is actually in profit (above the Open Price for buys) before it starts moving the Stop Loss. - If set to
false, it will tighten the Stop Loss immediately to reduce risk, even if the trade is currently negative.
- If set to
- Normalization: The code uses
NormalizeDouble(..., Digits)to ensure the calculated price matches the currency pair's precision (e.g., 5 decimal places for EURUSD), preventing "Invalid Price" errors. - StopLevel Check: The code checks
MarketInfo(Symbol(), MODE_STOPLEVEL)to ensure it doesn't try to place a Stop Loss too close to the current price, which would cause the broker to reject the modification (Error 130).
How to Use
- Open MetaEditor in MT4 (Press F4).
- Create a new Expert Advisor, name it "VolatilityTrailingStop".
- Paste the code above into the file, replacing everything.
- Click Compile.
- Go back to MT4, drag the EA onto a chart, and ensure "Allow Live Trading" is checked.
Q&A
Q: Why use ATR instead of a fixed number of pips?
A: Fixed pips do not account for market conditions. 20 pips might be a huge distance in a quiet Asian session but insignificant during a major news release. ATR adapts automatically: the Stop Loss widens when the market is volatile (preventing premature stop-outs) and tightens when the market is quiet.
Q: Can I use this logic on existing manual trades?
A: Yes. If you set MagicNumber to 0 in the inputs, the EA will manage all manual trades placed on that specific chart symbol.
Q: What is Error 130 and how does this code prevent it?
A: Error 130 is "Invalid Stops." It happens if you try to place a Stop Loss too close to the current Bid/Ask price. This code checks the broker's MODE_STOPLEVEL before attempting to modify the order to prevent this error.