Problem Description
Solution
Here is a complete, fully functional Expert Advisor (EA) for MetaTrader 4.
This EA uses a simple Moving Average Crossover strategy to determine entry points, but its primary focus is using the Average True Range (ATR) to calculate dynamic Stop Loss and Take Profit levels based on market volatility.
MQL4 Code
//+------------------------------------------------------------------+
//| ATR_Risk_EA.mq4 |
//| Copyright 2023, MetaQuotes |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Assistant"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input string RiskSettings = "--- Risk Settings ---"; // Section Title
input double InpLotSize = 0.1; // Trade Volume
input int InpATRPeriod = 14; // ATR Period
input double InpSLMultiplier= 1.5; // Stop Loss Multiplier (x ATR)
input double InpTPMultiplier= 2.0; // Take Profit Multiplier (x ATR)
input string StrategySettings = "--- Strategy Settings ---"; // Section Title
input int InpFastMA = 10; // Fast Moving Average Period
input int InpSlowMA = 20; // Slow Moving Average Period
input int InpMagicNum = 123456; // Magic Number
input int InpSlippage = 3; // Max Slippage (pips)
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Basic validation
if(InpATRPeriod <= 0 || InpFastMA <= 0 || InpSlowMA <= 0)
{
Print("Error: Periods must be greater than 0.");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if we already have an open trade for this symbol/magic number
if(CountOpenTrades() > 0) return;
// 2. Define Indicators
// ATR for volatility calculation
double atrValue = iATR(NULL, 0, InpATRPeriod, 0);
// Moving Averages for Entry Signal (Shift 1 = closed bar, Shift 2 = previous closed bar)
double fastMA_curr = iMA(NULL, 0, InpFastMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowMA_curr = iMA(NULL, 0, InpSlowMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double fastMA_prev = iMA(NULL, 0, InpFastMA, 0, MODE_EMA, PRICE_CLOSE, 2);
double slowMA_prev = iMA(NULL, 0, InpSlowMA, 0, MODE_EMA, PRICE_CLOSE, 2);
// 3. Calculate Dynamic SL and TP distances based on ATR
double slDistance = atrValue * InpSLMultiplier;
double tpDistance = atrValue * InpTPMultiplier;
// 4. Trading Logic (Crossover)
// Buy Signal: Fast MA crosses above Slow MA
if(fastMA_curr > slowMA_curr && fastMA_prev <= slowMA_prev)
{
// Calculate Price Levels
double entryPrice = Ask;
double stopLoss = NormalizeDouble(entryPrice - slDistance, Digits);
double takeProfit = NormalizeDouble(entryPrice + tpDistance, Digits);
// Send Order
int ticket = OrderSend(Symbol(), OP_BUY, InpLotSize, entryPrice, GetSlippage(), stopLoss, takeProfit, "ATR Buy", InpMagicNum, 0, clrBlue);
if(ticket < 0) Print("OrderSend failed with error #", GetLastError());
}
// Sell Signal: Fast MA crosses below Slow MA
if(fastMA_curr < slowMA_curr && fastMA_prev >= slowMA_prev)
{
// Calculate Price Levels
double entryPrice = Bid;
double stopLoss = NormalizeDouble(entryPrice + slDistance, Digits);
double takeProfit = NormalizeDouble(entryPrice - tpDistance, Digits);
// Send Order
int ticket = OrderSend(Symbol(), OP_SELL, InpLotSize, entryPrice, GetSlippage(), stopLoss, takeProfit, "ATR Sell", InpMagicNum, 0, clrRed);
if(ticket < 0) Print("OrderSend failed with error #", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Helper: Count open trades for this EA |
//+------------------------------------------------------------------+
int CountOpenTrades()
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNum)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
count++;
}
}
}
return(count);
}
//+------------------------------------------------------------------+
//| Helper: Adjust Slippage for 4/5 digit brokers |
//+------------------------------------------------------------------+
int GetSlippage()
{
if(Digits == 3 || Digits == 5) return(InpSlippage * 10);
return(InpSlippage);
}
//+------------------------------------------------------------------+
How It Works
-
Inputs:
InpATRPeriod: Determines how many candles back the ATR calculates volatility (Standard is 14).InpSLMultiplier: Multiplies the ATR value to set the Stop Loss distance. (e.g., if ATR is 0.0020 and multiplier is 1.5, SL is 30 pips away).InpTPMultiplier: Multiplies the ATR value to set the Take Profit distance.
-
Entry Logic:
- The EA uses a standard Moving Average Crossover (Fast EMA crossing Slow EMA) to decide when to enter. This ensures the EA actually trades so you can test the ATR logic.
-
ATR Calculation:
iATR(NULL, 0, InpATRPeriod, 0)retrieves the ATR value for the current symbol and timeframe.- The Stop Loss distance is calculated as
ATR * SL_Multiplier. - The Take Profit distance is calculated as
ATR * TP_Multiplier.
-
Price Normalization:
- The code uses
NormalizeDouble(..., Digits)on the calculated SL and TP prices. This is crucial in MQL4; without it, the server may reject the order because the calculated price has too many decimal places.
- The code uses
-
Execution:
- The
OrderSendfunction places the trade with the calculated SL and TP immediately attached to the order.
- The
How to Use
- Open MetaEditor in MT4 (Press F4).
- Create a new Expert Advisor file.
- Paste the code above into the file.
- Compile the file (Press F7).
- Go back to the MT4 Terminal, find ATR_Risk_EA in the Navigator, and drag it onto a chart.
Q&A
Q: Why use ATR for Stop Loss?
A: ATR (Average True Range) measures market volatility. Using a fixed pip stop loss (e.g., 20 pips) might be too tight in a volatile market (getting stopped out by noise) or too wide in a quiet market (risking too much). ATR adjusts the stop distance dynamically based on how much the price is currently moving.
Q: Can I use this on any timeframe?
A: Yes. Since the code uses Period() (represented by 0 in the iATR function), it automatically adapts to M15, H1, D1, etc. Note that ATR values on D1 are much larger than on M1, so your stops will naturally be wider on higher timeframes.
Q: What happens if the broker is ECN?
A: This code places SL/TP directly within the OrderSend function. Most modern MT4 bridges handle this fine. However, strictly speaking, some old ECN bridges require you to open the trade with 0 SL/TP first, and then use OrderModify to add them. This code uses the standard method for simplicity and compatibility with standard accounts.