Problem Description
Solution
Here is a complete, fully commented, and functional MQL4 Expert Advisor (trading robot) based on the Williams' Percent Range (WPR) indicator.
Strategy Logic
The Williams' Percent Range oscillates between 0 and -100.
- Buy Signal: The indicator is considered Oversold when it is below -80. The robot buys when the line crosses back above -80 (indicating momentum is returning upwards).
- Sell Signal: The indicator is considered Overbought when it is above -20. The robot sells when the line crosses back below -20 (indicating momentum is turning downwards).
- Execution: The robot works on "Closed Bars" (Shift 1 and 2) to prevent "repainting" (where a signal appears and disappears within the same candle).
MQL4 Code
//+------------------------------------------------------------------+
//| SimpleWPR.mq4 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input int WPR_Period = 14; // Williams' Percent Range Period
input double Overbought_Level = -20.0; // Level to trigger Sell (e.g., -20)
input double Oversold_Level = -80.0; // Level to trigger Buy (e.g., -80)
input double LotSize = 0.1; // Fixed Lot Size
input int StopLoss = 50; // Stop Loss in points (0 = no SL)
input int TakeProfit = 100; // Take Profit in points (0 = no TP)
input int MagicNumber = 123456; // Unique ID for this EA's trades
input int Slippage = 3; // Max slippage in points
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Basic validation
if(Oversold_Level > Overbought_Level)
{
Alert("Error: Oversold level must be lower than Overbought level (remember WPR is negative).");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if we are allowed to trade
if(!IsTradeAllowed()) return;
// 2. Calculate WPR values
// We look at Shift 1 (previous closed candle) and Shift 2 (candle before that)
// to detect a "Crossover" event.
double wpr1 = iWPR(NULL, 0, WPR_Period, 1);
double wpr2 = iWPR(NULL, 0, WPR_Period, 2);
// 3. Check for Buy Signal
// Logic: WPR was below -80 (Oversold) and has now crossed above -80
if(wpr2 < Oversold_Level && wpr1 > Oversold_Level)
{
// Check if we already have a Buy position to avoid stacking
if(CountOrders(OP_BUY) == 0)
{
// Close any existing Sell positions first (Reversal logic)
CloseAllOrders(OP_SELL);
// Open Buy
OpenTrade(OP_BUY);
}
}
// 4. Check for Sell Signal
// Logic: WPR was above -20 (Overbought) and has now crossed below -20
if(wpr2 > Overbought_Level && wpr1 < Overbought_Level)
{
// Check if we already have a Sell position
if(CountOrders(OP_SELL) == 0)
{
// Close any existing Buy positions first
CloseAllOrders(OP_BUY);
// Open Sell
OpenTrade(OP_SELL);
}
}
}
//+------------------------------------------------------------------+
//| Helper: Open a Trade |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
double price, sl, tp;
color cmdColor;
if(type == OP_BUY)
{
price = Ask;
// Calculate SL/TP for Buy
sl = (StopLoss > 0) ? price - StopLoss * Point : 0;
tp = (TakeProfit > 0) ? price + TakeProfit * Point : 0;
cmdColor = clrBlue;
}
else // OP_SELL
{
price = Bid;
// Calculate SL/TP for Sell
sl = (StopLoss > 0) ? price + StopLoss * Point : 0;
tp = (TakeProfit > 0) ? price - TakeProfit * Point : 0;
cmdColor = clrRed;
}
// Send the order
int ticket = OrderSend(Symbol(), type, LotSize, price, Slippage, sl, tp, "WPR Bot", MagicNumber, 0, cmdColor);
if(ticket < 0)
{
Print("OrderSend failed with error #", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Helper: Count Open Orders by Type |
//+------------------------------------------------------------------+
int CountOrders(int type)
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
// Filter by Symbol, Magic Number, and Type
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
{
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Helper: Close All Orders of a Specific Type |
//+------------------------------------------------------------------+
void CloseAllOrders(int type)
{
for(int i = OrdersTotal() - 1; i >= 0; i--) // Loop backwards when closing
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool res = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrGray);
if(!res) Print("Failed to close order: ", GetLastError());
}
}
}
}
//+------------------------------------------------------------------+
How to Install and Run
- Open MetaTrader 4.
- Press F4 to open the MetaEditor.
- Click New -> Expert Advisor (template) -> Next.
- Name it
SimpleWPRand click Next/Finish. - Delete everything in the new file and paste the code above.
- Press F7 or click Compile. Check the "Errors" tab at the bottom to ensure there are 0 errors.
- Go back to the main MT4 window.
- Find
SimpleWPRin the Navigator panel (under Expert Advisors). - Drag it onto a chart (e.g., EURUSD H1).
- Ensure the "AutoTrading" button at the top of MT4 is Green (On) and check "Allow live trading" in the EA inputs tab.
Key Features of This Code
- Crossover Logic: Instead of buying continuously while the price is oversold, it waits for the price to exit the oversold zone. This is safer as it confirms a potential reversal.
- Magic Number: It uses a unique ID (
MagicNumber) so it only manages its own trades and ignores manual trades or other robots running on the same account. - Risk Management: Includes inputs for Stop Loss and Take Profit.
- Position Management: It automatically closes opposite trades. For example, if a Sell signal occurs while a Buy is open, it closes the Buy and opens a Sell.
Q&A: Quantitative Trading with WPR
Q: Why use WPR over RSI for this strategy?
A: Williams' Percent Range is very similar to the Stochastic Oscillator. It is often more sensitive to price spikes than RSI. While RSI measures the speed and change of price movements, WPR measures the current close relative to the high-low range over a set period. This makes WPR excellent for identifying immediate reversal points in ranging markets.
Q: How can I optimize this strategy?
A: You can use the MT4 Strategy Tester (Ctrl+R). Set the "Optimization" checkbox and select the WPR_Period, StopLoss, and TakeProfit parameters. The optimizer will run thousands of combinations to find which settings yielded the best profit factor for your specific currency pair and timeframe.
Q: What is the risk of using this robot on a trending market?
A: Oscillators like WPR are "mean-reverting." In a strong uptrend, the indicator can stay "Overbought" (above -20) for a long time. This robot might sell too early (trying to catch a top) and get stopped out as the trend continues up. To fix this, quantitative traders often add a "Trend Filter" (like a 200-period Moving Average) and only take Buy signals if the price is above the Moving Average.