How to Build a Support and Resistance EA in MQL5?

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 input keyword, allowing users to configure the EA without modifying the code.
  • Order Management: Functions like OrderSend, OrderModify, OrderClose, and structures like MqlTradeRequest and MqlTradeResult for 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

  1. Open MetaEditor.
  2. Go to File -> New.
  3. Select “Expert Advisor (template)”.
  4. Click Next.
  5. Enter a name for your EA (e.g., SupportResistanceEA).
  6. Optionally, provide author and link details.
  7. Click Next and then Finish. 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.

  1. Open the Strategy Tester (View -> Strategy Tester or Ctrl+R).
  2. Select your EA (SupportResistanceEA.mq5).
  3. Choose the desired Symbol and Timeframe.
  4. Select a date range for backtesting.
  5. Choose the Modeling type (e.g., “Every tick based on real ticks” for highest accuracy).
  6. Configure your input parameters.
  7. Click Start. Analyze the results in the Results, Graph, and Backtest tabs.

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.

  1. In the Strategy Tester, check the Optimization box.
  2. Choose the type of optimization (e.g., “Fast genetic based algorithm”).
  3. Go to the “Inputs” tab and configure ranges (Start, Step, Stop) for the parameters you want to optimize (e.g., StopLossPips, TakeProfitPips, FractalsPeriod).
  4. Select the criteria to optimize for (e.g., Maximal Profit, Profit Factor).
  5. Click Start. The tester will run multiple backtests.
  6. 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.


Leave a Reply