Support and Resistance (S&R) are fundamental concepts in technical analysis, representing price levels where the likelihood of a price reversal or pause increases significantly. Identifying these levels is a cornerstone for many manual trading strategies. Automating strategies based on S&R requires translating these visual concepts into programmatic logic.
Understanding Support and Resistance Levels
- Support: A price level where falling prices have previously stopped and reversed. It suggests a concentration of buying interest.
- Resistance: A price level where rising prices have previously stopped and reversed. It suggests a concentration of selling interest.
These levels are not precise lines but rather zones. They can be dynamic, changing over time, and their significance often depends on the timeframe being analyzed.
Importance of Support and Resistance in Automated Trading
For Expert Advisors (EAs), accurately identifying and utilizing S&R levels offers several advantages:
- Defined Entry/Exit Points: S&R levels provide clear potential areas to enter long (at support) or short (at resistance) positions, as well as logical places for setting stop loss (SL) and take profit (TP) orders.
- Risk Management: Placing SL orders just below support (for longs) or just above resistance (for shorts) can help define maximum potential loss per trade.
- Strategic Framework: S&R forms a robust basis for building various trading strategies, such as range trading (between S&R) or breakout strategies (trading moves after S&R is breached).
Automating this process in MQL5 allows for systematic execution, backtesting, and optimization, removing emotional biases inherent in manual trading.
Basic Concepts of MQL5 for EA Development
Developing an EA in MQL5 involves understanding key components:
OnInit(): The initialization function, executed once when the EA is attached to a chart.OnDeinit(): The deinitialization function, executed when the EA is removed or the terminal closes.OnTick(): The event handler for new tick arrivals, where most trading logic resides.OnTrade(): Event handler for trade-related events (order fill, modification, etc.).- Global Variables: Variables accessible throughout the EA’s code.
- Input Parameters: External variables defined with the
inputkeyword, allowing users to configure the EA without modifying the code. - Order Management: Functions like
OrderSend,OrderModify,OrderClose, and structures likeMqlTradeRequestandMqlTradeResultfor executing trades. - Technical Indicators: Using built-in or custom indicators via functions like
iFractals,iHigh,iLow, etc.
MQL5 differs significantly from MQL4 in its event handling model and order management system, using trade requests and asynchronous operations rather than direct OrderSend with complex return values.
Setting Up the MQL5 Environment for the EA
Creating an MQL5 Expert Advisor project is straightforward within the MetaTrader 5 integrated development environment.
Installing MetaTrader 5 and MQL5 Editor
Ensure you have MetaTrader 5 installed. The MQL5 Editor (MetaEditor) is bundled with the terminal. Launch MetaEditor directly or via the “MetaEditor” button in the MetaTrader 5 terminal’s toolbar (or press F4).
Creating a New Expert Advisor Project
- Open MetaEditor.
- Go to
File->New. - Select “Expert Advisor (template)”.
- Click
Next. - Enter a name for your EA (e.g.,
SupportResistanceEA). - Optionally, provide author and link details.
- Click
Nextand thenFinish. MetaEditor will create the project structure and open the template code.
Defining EA Properties and Inputs
At the beginning of the .mq5 file, define the EA’s properties and user-configurable input parameters using the #property directive and input keyword.
//+------------------------------------------------------------------+
//| SupportResistanceEA.mq5 |
//| Your Name |
//| Your Website |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property link "Your Website"
#property version "1.00"
#property description "EA based on Support and Resistance levels."
#property strict
//--- input parameters
input double RiskPercent = 1.0; // Risk per trade (%) - This is a basic input example
input int StopLossPips = 20; // Stop Loss in pips
input int TakeProfitPips = 40; // Take Profit in pips
input int FractalsPeriod = 5; // Period for Fractals indicator
// Add more inputs as needed, e.g., minimum distance for S&R, timeframe filters, etc.
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//---
return(INIT_SUCCEEDED);
}
//...
These inputs allow traders to customize risk, SL/TP levels, indicator periods, and other strategy parameters without recompiling the code.
Coding the Support and Resistance Detection Logic
The core challenge is programmatically identifying significant S&R levels on the chart. There are multiple approaches, from simple methods based on historical highs/lows to using specific indicators or more complex pattern recognition.
Implementing Functions to Identify Support and Resistance Levels
A common basic method involves looking for swing points, often identifiable using the Fractals indicator. Fractals mark local highs and lows within a defined window.
Let’s outline a simplified function to find recent support and resistance levels based on fractals:
// Structure to hold S&R level information
struct SR_Level
{
double price;
datetime time;
int bar_index;
};
// Function to find recent fractal levels
void FindFractalSR(int barsToSearch, int fractalPeriod,
SR_Level& recentSupport, SR_Level& recentResistance)
{
MqlRates rates[];
int counted_bars = CopyRates(Symbol(), Period(), 0, barsToSearch, rates);
if(counted_bars <= fractalPeriod) return;
double high_levels[];
double low_levels[];
// Get fractal indicator handles
int high_handle = iFractals(Symbol(), Period(), INDICATOR_FRACTAL_HIGH);
int low_handle = iFractals(Symbol(), Period(), INDICATOR_FRACTAL_LOW);
// Copy indicator buffer data
CopyBuffer(high_handle, 0, 0, barsToSearch, high_levels);
CopyBuffer(low_handle, 0, 0, barsToSearch, low_levels);
recentResistance.price = EMPTY_VALUE;
recentSupport.price = EMPTY_VALUE;
// Iterate backwards through bars to find the most recent levels
for(int i = fractalPeriod; i < counted_bars; i++)
{
// Check for Resistance (High Fractal)
if(high_levels[i] > 0.0 && recentResistance.price == EMPTY_VALUE)
{
recentResistance.price = NormalizeDouble(rates[i].high, _Digits);
recentResistance.time = rates[i].time;
recentResistance.bar_index = i;
// Optional: Add a check here to ensure the fractal is 'confirmed'
}
// Check for Support (Low Fractal)
if(low_levels[i] > 0.0 && recentSupport.price == EMPTY_VALUE)
{
recentSupport.price = NormalizeDouble(rates[i].low, _Digits);
recentSupport.time = rates[i].time;
recentSupport.bar_index = i;
// Optional: Add a check here to ensure the fractal is 'confirmed'
}
// Stop searching once both levels are found
if(recentSupport.price != EMPTY_VALUE && recentResistance.price != EMPTY_VALUE) break;
}
// Invalidate levels if not found
if(recentSupport.price == EMPTY_VALUE) recentSupport.bar_index = -1;
if(recentResistance.price == EMPTY_VALUE) recentResistance.bar_index = -1;
}
This function needs to be called within OnTick(). You would typically search a fixed number of recent bars (barsToSearch) to find the most relevant levels. Remember that Fractal high needs 2 bars after the high and Fractal low needs 2 bars after the low to be confirmed. The simple check high_levels[i] > 0.0 only tells you if the indicator buffered a value, not if it’s fully confirmed on the chart.
Using Price Action and Indicators (e.g., Fractals) for Confirmation
While the basic fractal method finds swing points, true S&R levels are often where price reacts to these points repeatedly or significantly. More advanced techniques could involve:
- Clustering: Identifying price zones where multiple historical swing points converge.
- Volume Analysis: Looking for increased volume at potential S&R levels (more complex in spot forex).
- Indicator Confluence: Using other indicators (e.g., moving averages, pivot points, Fibonacci levels) to confirm the significance of a level.
For instance, you might only consider a fractal low a strong support if price tested that level multiple times without breaking through.
Handling False Breakouts and Filtering Signals
False breakouts are a major challenge. Price might briefly cross an S&R level only to reverse. To mitigate this:
- Confirmation Filter: Require a candle to close above resistance or below support for a breakout signal.
- Time Filter: Wait a certain number of bars after a potential breakout before entering.
- Volatility Filter: Use indicators like ATR to gauge market volatility and require a breakout to exceed a volatility threshold.
- Multiple Timeframe Analysis: Confirm S&R levels and breakouts on higher timeframes.
This often means refining the entry logic to not simply trigger on the first touch or slight breach of a calculated level.
Implementing Trading Strategies Based on Support and Resistance
Once S&R levels are identified, the next step is to translate them into actionable trading signals and manage positions.
Entry Rules: Buying at Support and Selling at Resistance
A classic S&R strategy involves trading reversals:
- Buy at Support: When price approaches and shows signs of bouncing off a identified support level.
- Sell at Resistance: When price approaches and shows signs of bouncing off a identified resistance level.
Alternatively, breakout strategies involve trading when price breaks through a level:
- Buy Breakout: When price convincingly breaks above resistance.
- Sell Breakout: When price convincingly breaks below support.
The implementation in OnTick() would involve checking the current price relative to the identified recentSupport and recentResistance levels. You’ll need to incorporate the filtering logic discussed earlier.
// Inside OnTick()
SR_Level support, resistance;
FindFractalSR(100, FractalsPeriod, support, resistance); // Search last 100 bars
if(support.bar_index != -1 && resistance.bar_index != -1)
{
double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double bidPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID);
// Simple example: Buy if close price bounced off support
// Needs refinement for actual bouncing logic (e.g., hammer candle at support)
// For simplicity, let's check if current price is near support and we don't have positions
double tolerance = 5 * _Point; // Example tolerance
if(MathAbs(currentPrice - support.price) <= tolerance && PositionsTotal() == 0)
{
// Send Buy Order (implementation details below)
Print("Attempting Buy near Support", support.price);
// ExecuteTrade(ORDER_TYPE_BUY, ...);
}
// Simple example: Sell if close price bounced off resistance
// For simplicity, let's check if current price is near resistance and we don't have positions
if(MathAbs(bidPrice - resistance.price) <= tolerance && PositionsTotal() == 0)
{
// Send Sell Order (implementation details below)
Print("Attempting Sell near Resistance", resistance.price);
// ExecuteTrade(ORDER_TYPE_SELL, ...);
}
// Add breakout logic here if desired
}
Exit Rules: Setting Stop Loss and Take Profit Levels
S&R levels provide natural places for SL and TP:
- Buy Trade: SL typically placed just below the support level. TP could be at the resistance level or a calculated risk/reward ratio distance.
- Sell Trade: SL typically placed just above the resistance level. TP could be at the support level or a calculated risk/reward ratio distance.
Using the input parameters StopLossPips and TakeProfitPips, calculate the absolute price levels:
// Function to send a trade request
void ExecuteTrade(ENUM_ORDER_TYPE orderType, double volume, double price, double sl, double tp)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = Symbol();
request.volume = volume;
request.type = orderType;
request.price = price; // Use Ask for Buy, Bid for Sell or Market
request.deviation= 10; // Max price deviation
request.magic = YOUR_MAGIC_NUMBER; // Use a unique magic number for your EA
// Calculate Stop Loss and Take Profit prices
double sl_price = 0;
double tp_price = 0;
double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
if (orderType == ORDER_TYPE_BUY)
{
sl_price = NormalizeDouble(price - StopLossPips * point, _Digits);
tp_price = NormalizeDouble(price + TakeProfitPips * point, _Digits);
}
else if (orderType == ORDER_TYPE_SELL)
{
sl_price = NormalizeDouble(price + StopLossPips * point, _Digits);
tp_price = NormalizeDouble(price - TakeProfitPips * point, _Digits);
}
request.sl = sl_price;
request.tp = tp_price;
PrintFormat("Sending %s order: %.2f at %.5f, SL %.5f, TP %.5f",
EnumToString(orderType), volume, price, sl_price, tp_price);
// Send the request
OrderSend(request, result);
// Check the result
if(result.retcode == TRADE_RETCODE_NO_ERROR)
{
Print("Order executed successfully. Order Ticket: ", result.order);
}
else
{
PrintFormat("Order execution failed. Return code: %d, Description: %s",
result.retcode, TerminalInfoString(TERMINAL_INFO_LAST_ERROR));
}
}
This ExecuteTrade function can be called from within your OnTick logic when an entry signal is detected. You’ll need to define YOUR_MAGIC_NUMBER as a unique integer.
Position Sizing and Risk Management Techniques
Fixed lot sizing is simple but risky. A better approach is dynamic position sizing based on a percentage of account equity or balance, taking the stop loss distance into account.
// Function to calculate lot size based on risk percentage and SL distance
double CalculateLotSize(double riskPercent, int stopLossPips)
{
if (riskPercent <= 0 || stopLossPips <= 0) return 0;
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = accountBalance * riskPercent / 100.0;
double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
double tickValue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE);
// Value of 1 pip movement per standard lot (100,000 units)
// This can vary depending on instrument type and quote currency
// A more robust calculation considers SYMBOL_TRADE_CONTRACT_SIZE and SYMBOL_POINT/SYMBOL_TRADE_TICK_SIZE
double pipValuePerLot = tickValue / tickSize * 10; // General approximation for 5-digit pairs
// Correct calculation considering contract size
double contractSize = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_CONTRACT_SIZE);
pipValuePerLot = contractSize * point;
// Need to adjust pipValuePerLot if the account currency is not the quote currency of the symbol
// This involves finding the exchange rate between the quote currency and account currency
string quoteCurrency = SymbolInfoString(Symbol(), SYMBOL_CURRENCY_QUOTE);
string accountCurrency = AccountInfoString(ACCOUNT_CURRENCY);
if (quoteCurrency != accountCurrency)
{
double rate = 0;
// Find exchange rate, e.g., USDJPY quote=JPY, account=USD, need USDJPY price
// EURUSD quote=USD, account=EUR, need EURUSD price (inverse)
string cross = quoteCurrency + accountCurrency;
double cross_bid = SymbolInfoDouble(cross, SYMBOL_BID);
if (cross_bid > 0) rate = cross_bid;
else
{
cross = accountCurrency + quoteCurrency;
double cross_ask = SymbolInfoDouble(cross, SYMBOL_ASK);
if (cross_ask > 0) rate = 1.0 / cross_ask;
else Print("Could not find exchange rate for \"", quoteCurrency, "\" to \"", accountCurrency, "\"");
}
if (rate > 0) pipValuePerLot *= rate;
}
double stopLossDistance = stopLossPips * point; // SL distance in price points
double valuePerPipMovement = pipValuePerLot; // Value of 1 pip for 1 standard lot
// Risk per Lot = StopLossDistance * ValuePerPipPerLot / Point
// The risk amount calculation needs to be in the account currency
// Risk per Lot (in Account Currency) = StopLossPips * pipValuePerLot
if (pipValuePerLot <= 0) return 0;
double lotSize = riskAmount / (stopLossPips * pipValuePerLot);
// Get min/max lot size and step
double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
// Adjust lot size to be within bounds and in increments of lotStep
lotSize = fmax(minLot, lotSize);
lotSize = fmin(maxLot, lotSize);
lotSize = NormalizeDouble(floor(lotSize / lotStep) * lotStep, 2); // Assume lot step is 0.01 or 0.1
return lotSize;
}
This function provides a basis for dynamic lot sizing. Call this function before sending an order to determine the appropriate volume.
Testing, Optimization, and Deployment
Building the EA is only half the battle. Thorough testing and optimization are crucial before live trading.
Backtesting the EA using the Strategy Tester
MetaTrader 5’s Strategy Tester is a powerful tool for evaluating historical performance.
- Open the Strategy Tester (
View->Strategy Testeror Ctrl+R). - Select your EA (
SupportResistanceEA.mq5). - Choose the desired Symbol and Timeframe.
- Select a date range for backtesting.
- Choose the Modeling type (e.g., “Every tick based on real ticks” for highest accuracy).
- Configure your input parameters.
- Click
Start. Analyze the results in theResults,Graph, andBacktesttabs.
Pay close attention to metrics like Profit Factor, Drawdown, and the shape of the Equity Curve.
Optimizing EA Parameters for Different Market Conditions
Optimization allows the Strategy Tester to automatically test different combinations of your EA’s input parameters to find those yielding the best historical performance.
- In the Strategy Tester, check the
Optimizationbox. - Choose the type of optimization (e.g., “Fast genetic based algorithm”).
- Go to the “Inputs” tab and configure ranges (
Start,Step,Stop) for the parameters you want to optimize (e.g.,StopLossPips,TakeProfitPips,FractalsPeriod). - Select the criteria to optimize for (e.g.,
Maximal Profit,Profit Factor). - Click
Start. The tester will run multiple backtests. - Analyze the optimization results in the “Optimization Results” tab. Sort by your chosen criteria.
Caution: Over-optimization (curve fitting) is a significant risk. Parameters that perform best on historical data may fail in live trading. Test optimized parameters on out-of-sample data periods or use forward testing.
Forward Testing and Live Deployment Considerations
- Forward Testing: Run the EA on a demo account for a significant period (weeks to months) under live market conditions. This is the best way to see if the EA performs as expected outside of historical data.
- Broker Differences: Be aware that execution speeds, spreads, commission, and available historical data can vary between brokers, affecting EA performance.
- VPS: For reliable execution 24/7, run your MetaTrader 5 terminal and EA on a Virtual Private Server (VPS).
- Monitoring: Even automated systems require monitoring. Keep an eye on performance, unexpected errors, or drastic changes in market conditions that might invalidate the strategy.
- Logging: Implement robust logging within your EA to track its decisions, trades, and any errors. This is invaluable for debugging and understanding its behavior.
Building a robust Support and Resistance EA in MQL5 requires careful planning, solid coding, and rigorous testing. By translating the concepts of S&R into logical steps and utilizing MQL5’s capabilities, you can create automated strategies that potentially capitalize on these key market levels.