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.
Historical test results
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, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom EURUSD/GBPUSD news-trading EA
Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
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.