MQL5 News Trading EA: Automate High-Impact Event Strategies
This page covers building a fully automated MQL5 Expert Advisor that trades high-impact economic news events on EURUSD and GBPUSD. The EA places pending stop orders in both directions ahead of key releases such as NFP, CPI, and central bank decisions, then cancels the unfilled side after one leg is triggered.
Backtest Performance
Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.
Strategy Logic
Entry Conditions
The EA places a BUY_STOP and SELL_STOP at a configurable offset (default 20 pips) above and below the current price starting a set number of minutes before a scheduled news event. Once one order fills, the EA immediately cancels the opposite pending order to avoid being caught in both directions. A minimum spread filter prevents order placement during abnormal pre-news liquidity conditions.
Exit Conditions
Each filled position carries a fixed take-profit of 30 pips and a stop-loss of 15 pips, producing a 2:1 reward-to-risk ratio by default. A time-based exit closes any open position that remains open longer than 60 minutes after the news release to avoid holding through the post-news drift phase. Trailing stop activation at 20 pips profit locks in gains when momentum continues beyond the initial target.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| NewsTrading_EA.mq5 |
//| MQL5 News Trading Expert Advisor for EURUSD / GBPUSD |
//| Pineify.app — AI-powered MQL5 & Pine Script code generation |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property link "https://pineify.app"
#property version "1.00"
#property strict
//--- Input parameters
input int NewsMinutesBefore = 5; // Minutes before news to place orders
input int NewsMinutesAfter = 60; // Minutes after news to force-close position
input double EntryOffsetPips = 20.0; // Pips above/below price for pending orders
input double TakeProfitPips = 30.0; // Take profit in pips
input double StopLossPips = 15.0; // Stop loss in pips
input double TrailingStartPips = 20.0; // Trailing stop activation in pips
input double TrailingStepPips = 5.0; // Trailing stop step in pips
input double LotSize = 0.10; // Fixed lot size
input double MinSpreadPips = 3.0; // Maximum spread allowed for entry (pips)
input string NewsEventTime = "08:30"; // News event time HH:MM (server time)
input ulong MagicNumber = 20240001;
//--- Global variables
double g_point = 0.0;
double g_pipSize = 0.0;
bool g_ordersPlaced = false;
bool g_positionOpen = false;
datetime g_newsTime = 0;
datetime g_fillTime = 0;
ulong g_buyStopTicket = 0;
ulong g_sellStopTicket = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
g_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_pipSize = (digits == 3 || digits == 5) ? g_point * 10.0 : g_point;
if(g_point <= 0.0)
{
Print("ERROR: Could not retrieve symbol point size for ", _Symbol);
return INIT_FAILED;
}
Print("NewsTrading EA initialized on ", _Symbol,
" | PipSize=", g_pipSize,
" | NewsTime=", NewsEventTime);
g_newsTime = ParseNewsTime(NewsEventTime);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
CancelPendingOrders();
Print("NewsTrading EA deinitialized. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
datetime now = TimeCurrent();
// Refresh news time each day
g_newsTime = ParseNewsTime(NewsEventTime);
long secondsToNews = (long)(g_newsTime - now);
long secondsAfterNews = (long)(now - g_newsTime);
double spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * g_point;
double spreadPips = spread / g_pipSize;
//--- Place pending orders before news
if(!g_ordersPlaced && !g_positionOpen)
{
if(secondsToNews >= 0 && secondsToNews <= (long)(NewsMinutesBefore * 60))
{
if(spreadPips <= MinSpreadPips)
{
PlacePendingOrders();
g_ordersPlaced = true;
}
else
{
Print("Spread too wide (", spreadPips, " pips). Skipping order placement.");
}
}
}
//--- Monitor pending orders: cancel unfilled side after one fills
if(g_ordersPlaced && !g_positionOpen)
{
CheckOrderFill();
}
//--- Manage open position: trailing stop and time exit
if(g_positionOpen)
{
ApplyTrailingStop();
if(secondsAfterNews >= (long)(NewsMinutesAfter * 60))
{
Print("Time limit reached after news. Closing all positions.");
CloseAllPositions();
ResetState();
}
}
//--- Reset state after news window has passed entirely
if(secondsAfterNews > (long)(NewsMinutesAfter * 60 + 300) && !g_positionOpen)
{
ResetState();
}
}
//+------------------------------------------------------------------+
//| Parse HH:MM string into today's datetime |
//+------------------------------------------------------------------+
datetime ParseNewsTime(string timeStr)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
string parts[];
StringSplit(timeStr, ':', parts);
if(ArraySize(parts) < 2) return 0;
dt.hour = (int)StringToInteger(parts[0]);
dt.min = (int)StringToInteger(parts[1]);
dt.sec = 0;
return StructToTime(dt);
}
//+------------------------------------------------------------------+
//| Place BUY_STOP and SELL_STOP pending orders |
//+------------------------------------------------------------------+
void PlacePendingOrders()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double buyEntry = ask + EntryOffsetPips * g_pipSize;
double sellEntry = bid - EntryOffsetPips * g_pipSize;
double buyTP = buyEntry + TakeProfitPips * g_pipSize;
double buySL = buyEntry - StopLossPips * g_pipSize;
double sellTP = sellEntry - TakeProfitPips * g_pipSize;
double sellSL = sellEntry + StopLossPips * g_pipSize;
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
buyEntry = NormalizeDouble(buyEntry, digits);
sellEntry = NormalizeDouble(sellEntry, digits);
buyTP = NormalizeDouble(buyTP, digits);
buySL = NormalizeDouble(buySL, digits);
sellTP = NormalizeDouble(sellTP, digits);
sellSL = NormalizeDouble(sellSL, digits);
MqlTradeRequest req = {};
MqlTradeResult res = {};
// BUY STOP
req.action = TRADE_ACTION_PENDING;
req.symbol = _Symbol;
req.volume = LotSize;
req.type = ORDER_TYPE_BUY_STOP;
req.price = buyEntry;
req.tp = buyTP;
req.sl = buySL;
req.magic = MagicNumber;
req.comment = "News BUY_STOP";
req.type_filling = ORDER_FILLING_IOC;
if(OrderSend(req, res))
g_buyStopTicket = res.order;
else
Print("BUY_STOP failed: retcode=", res.retcode);
ZeroMemory(req);
ZeroMemory(res);
// SELL STOP
req.action = TRADE_ACTION_PENDING;
req.symbol = _Symbol;
req.volume = LotSize;
req.type = ORDER_TYPE_SELL_STOP;
req.price = sellEntry;
req.tp = sellTP;
req.sl = sellSL;
req.magic = MagicNumber;
req.comment = "News SELL_STOP";
req.type_filling = ORDER_FILLING_IOC;
if(OrderSend(req, res))
g_sellStopTicket = res.order;
else
Print("SELL_STOP failed: retcode=", res.retcode);
Print("Pending orders placed: BUY_STOP #", g_buyStopTicket,
" SELL_STOP #", g_sellStopTicket);
}
//+------------------------------------------------------------------+
//| Check if one pending order filled; cancel the other |
//+------------------------------------------------------------------+
void CheckOrderFill()
{
bool buyFilled = !OrderSelect(g_buyStopTicket);
bool sellFilled = !OrderSelect(g_sellStopTicket);
// If buy stop no longer pending, it either filled or was cancelled
if(buyFilled && g_buyStopTicket != 0)
{
if(PositionSelectByTicket(g_buyStopTicket) ||
PositionSelect(_Symbol))
{
Print("BUY_STOP filled. Cancelling SELL_STOP #", g_sellStopTicket);
CancelOrder(g_sellStopTicket);
g_positionOpen = true;
g_fillTime = TimeCurrent();
}
}
if(sellFilled && g_sellStopTicket != 0)
{
if(PositionSelectByTicket(g_sellStopTicket) ||
PositionSelect(_Symbol))
{
Print("SELL_STOP filled. Cancelling BUY_STOP #", g_buyStopTicket);
CancelOrder(g_buyStopTicket);
g_positionOpen = true;
g_fillTime = TimeCurrent();
}
}
}
//+------------------------------------------------------------------+
//| Cancel a single pending order by ticket |
//+------------------------------------------------------------------+
void CancelOrder(ulong ticket)
{
if(ticket == 0) return;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_REMOVE;
req.order = ticket;
if(!OrderSend(req, res))
Print("Cancel order #", ticket, " failed: retcode=", res.retcode);
}
//+------------------------------------------------------------------+
//| Cancel all pending orders placed by this EA |
//+------------------------------------------------------------------+
void CancelPendingOrders()
{
CancelOrder(g_buyStopTicket);
CancelOrder(g_sellStopTicket);
}
//+------------------------------------------------------------------+
//| Apply trailing stop to open position |
//+------------------------------------------------------------------+
void ApplyTrailingStop()
{
if(!PositionSelect(_Symbol)) return;
if(PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) return;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
long posType = PositionGetInteger(POSITION_TYPE);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double newSL = currentSL;
if(posType == POSITION_TYPE_BUY)
{
double profitPips = (bid - openPrice) / g_pipSize;
if(profitPips >= TrailingStartPips)
{
double trailSL = NormalizeDouble(bid - TrailingStepPips * g_pipSize, digits);
if(trailSL > currentSL + g_pipSize)
newSL = trailSL;
}
}
else if(posType == POSITION_TYPE_SELL)
{
double profitPips = (openPrice - ask) / g_pipSize;
if(profitPips >= TrailingStartPips)
{
double trailSL = NormalizeDouble(ask + TrailingStepPips * g_pipSize, digits);
if(trailSL < currentSL - g_pipSize || currentSL == 0.0)
newSL = trailSL;
}
}
if(newSL != currentSL && newSL != 0.0)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.symbol = _Symbol;
req.sl = newSL;
req.tp = currentTP;
req.position = PositionGetInteger(POSITION_TICKET);
if(!OrderSend(req, res))
Print("Trailing SL update failed: retcode=", res.retcode);
}
}
//+------------------------------------------------------------------+
//| Close all open positions for this symbol and magic number |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
if(!PositionSelect(_Symbol)) return;
if(PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) return;
ulong ticket = PositionGetInteger(POSITION_TICKET);
double volume = PositionGetDouble(POSITION_VOLUME);
long posType = PositionGetInteger(POSITION_TYPE);
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = volume;
req.position = ticket;
req.type = (posType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (posType == POSITION_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.type_filling = ORDER_FILLING_IOC;
req.comment = "News EA time exit";
if(!OrderSend(req, res))
Print("Close position failed: retcode=", res.retcode);
}
//+------------------------------------------------------------------+
//| Reset EA state for next news event |
//+------------------------------------------------------------------+
void ResetState()
{
g_ordersPlaced = false;
g_positionOpen = false;
g_buyStopTicket = 0;
g_sellStopTicket= 0;
g_fillTime = 0;
}
//+------------------------------------------------------------------+Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.
Generate a Custom EURUSD/GBPUSD News-trading EA →
Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.