Re: Moving Average Bounce Scalping
Posted: Tue Sep 22, 2026 6:10 pm
1. MetaTrader 4 (MQL4) Expert Advisor
In MQL4, signal detection is evaluated on candle close (shift = 1) to avoid intra-bar false signals, while trailing stops are updated tick-by-tick. Save this code in the MetaEditor as an Expert Advisor (.mq4).
In MQL4, signal detection is evaluated on candle close (shift = 1) to avoid intra-bar false signals, while trailing stops are updated tick-by-tick. Save this code in the MetaEditor as an Expert Advisor (.mq4).
Code: Select all
//+------------------------------------------------------------------+
//| EMA_Pullback_Continuation.mq4|
//| Copyright 2026, Scalping Trader |
//+------------------------------------------------------------------+
#property strict
// --- Input Parameters ---
input int InpFastEMAPeriod = 20; // Fast EMA Period
input int InpSlowEMAPeriod = 50; // Slow EMA Period
input int InpMacroEMAPeriod = 200; // Macro 200 EMA Period
input int InpSwingLookback = 5; // Swing Lookback Bars for Initial SL
input int InpATRPeriod = 14; // ATR Period for Trailing Stop
input double InpATRMultiplier = 2.5; // ATR Trailing Multiplier
input double InpLotSize = 0.10; // Fixed Lot Size
input int InpSlippage = 3; // Max Slippage (Points)
input int InpMagicNumber = 102030; // Unique EA Identifier
// Global variable for new-bar detection
datetime glLastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage Active Positions (Trailing Stop on Every Tick)
ManageTrailingStop();
// 2. Check for New Bar Close (Signals evaluate on bar 1 completion)
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == glLastBarTime)
return; // Wait for current bar to close
glLastBarTime = currentBarTime;
// 3. Prevent Multiple Concurrent Positions
if(CountOpenOrders() > 0)
return;
// --- Indicator Values (Evaluated on Bar 1) ---
double fastEMA = iMA(_Symbol, _Period, InpFastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowEMA = iMA(_Symbol, _Period, InpSlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double macroEMA = iMA(_Symbol, _Period, InpMacroEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double barOpen = iOpen(_Symbol, _Period, 1);
double barClose = iClose(_Symbol, _Period, 1);
double barHigh = iHigh(_Symbol, _Period, 1);
double barLow = iLow(_Symbol, _Period, 1);
double barRange = barHigh - barLow;
if(barRange <= 0) return; // Prevent division by zero
// --- Trend Filters ---
bool bullishTrend = (barClose > macroEMA) && (fastEMA > slowEMA);
bool bearishTrend = (barClose < macroEMA) && (fastEMA < slowEMA);
// --- Conviction & Pullback Rules ---
// Long Setup
bool pulledBackLong = (barLow <= fastEMA) && (barClose > fastEMA);
bool bullishConviction = (barClose > barOpen) && ((barClose - barOpen) / barRange > 0.5);
// Short Setup
bool pulledBackShort = (barHigh >= fastEMA) && (barClose < fastEMA);
bool bearishConviction = (barOpen > barClose) && ((barOpen - barClose) / barRange > 0.5);
// --- Execution ---
if(bullishTrend && pulledBackLong && bullishConviction)
{
int lowestIndex = iLowest(_Symbol, _Period, MODE_LOW, InpSwingLookback, 1);
double stopLoss = NormalizeDouble(iLow(_Symbol, _Period, lowestIndex), _Digits);
double ask = MarketInfo(_Symbol, MODE_ASK);
if(stopLoss < ask)
{
int ticket = OrderSend(_Symbol, OP_BUY, InpLotSize, ask, InpSlippage, stopLoss, 0, "EMA Pullback Long", InpMagicNumber, 0, clrBlue);
if(ticket < 0)
Print("Error opening Buy order: ", GetLastError());
}
}
else if(bearishTrend && pulledBackShort && bearishConviction)
{
int highestIndex = iHighest(_Symbol, _Period, MODE_HIGH, InpSwingLookback, 1);
double stopLoss = NormalizeDouble(iHigh(_Symbol, _Period, highestIndex), _Digits);
double bid = MarketInfo(_Symbol, MODE_BID);
if(stopLoss > bid)
{
int ticket = OrderSend(_Symbol, OP_SELL, InpLotSize, bid, InpSlippage, stopLoss, 0, "EMA Pullback Short", InpMagicNumber, 0, clrRed);
if(ticket < 0)
Print("Error opening Sell order: ", GetLastError());
}
}
}
//+------------------------------------------------------------------+
//| Dynamic ATR Ratchet Trailing Stop |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
double atr = iATR(_Symbol, _Period, InpATRPeriod, 0);
double stopLevel = MarketInfo(_Symbol, MODE_STOPLEVEL) * _Point;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
{
if(OrderType() == OP_BUY)
{
double highPrice = iHigh(_Symbol, _Period, 0);
double calculatedTrail = NormalizeDouble(highPrice - (atr * InpATRMultiplier), _Digits);
double bid = MarketInfo(_Symbol, MODE_BID);
// Ratchet check: Only move stop up, respect broker minimum stop level
if(calculatedTrail > OrderStopLoss() && (bid - calculatedTrail) > stopLevel)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), calculatedTrail, OrderTakeProfit(), 0, clrBlue);
if(!res) Print("Error modifying Buy SL: ", GetLastError());
}
}
else if(OrderType() == OP_SELL)
{
double lowPrice = iLow(_Symbol, _Period, 0);
double calculatedTrail = NormalizeDouble(lowPrice + (atr * InpATRMultiplier), _Digits);
double ask = MarketInfo(_Symbol, MODE_ASK);
// Ratchet check: Only move stop down, respect broker minimum stop level
if((OrderStopLoss() == 0.0 || calculatedTrail < OrderStopLoss()) && (calculatedTrail - ask) > stopLevel)
{
bool res = OrderModify(OrderTicket(), OrderOpenPrice(), calculatedTrail, OrderTakeProfit(), 0, clrRed);
if(!res) Print("Error modifying Sell SL: ", GetLastError());
}
}
}
}
}
//+------------------------------------------------------------------+
//| Count active positions for this EA |
//+------------------------------------------------------------------+
int CountOpenOrders()
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
count++;
}
}
return count;
}