How to handle execution latency and broker slippage?
How to handle execution latency and broker slippage?
Hi traders, scalpers,
today i would like to open topic which can save for all of you some money in real trading enviroment.
I think that allmost all of you allready know, that hidden profit eaters from your trading account can be execution latency and broker's slippage.
Thats why it is really important to choose your broker wisely and VPS location or type of internet connection.
When backtesting high-frequency or scalping strategies in MQL5, ignoring execution latency and variable slippage is the fastest way to turn a profitable backtest into a live trading drawdown.
In live ECN environments (such as Equinix LD4 infrastructure used by institutional raw-spread brokers), your EA does not execute at the exact price on your screen. Between the moment your code generates a signal and the broker's matching engine fills the order, two critical things happen: Network & Server Latency and Liquidity Microstructure Slippage.
today i would like to open topic which can save for all of you some money in real trading enviroment.
I think that allmost all of you allready know, that hidden profit eaters from your trading account can be execution latency and broker's slippage.
Thats why it is really important to choose your broker wisely and VPS location or type of internet connection.
When backtesting high-frequency or scalping strategies in MQL5, ignoring execution latency and variable slippage is the fastest way to turn a profitable backtest into a live trading drawdown.
In live ECN environments (such as Equinix LD4 infrastructure used by institutional raw-spread brokers), your EA does not execute at the exact price on your screen. Between the moment your code generates a signal and the broker's matching engine fills the order, two critical things happen: Network & Server Latency and Liquidity Microstructure Slippage.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
1. Modeling Execution Latency (The Time Delta)
Latency creates a time-lag between the trigger price ($P_{signal}$) and the execution price ($P_{exec}$). In fast-moving markets, a 30ms to 100ms round-trip delay means the price has already moved against you.
The Reality: MT5’s standard Strategy Tester assumes instant zero-latency execution by default. If your strategy relies on fast breakouts or micro-pullbacks, instant fills allow your backtest to capture liquidity that was already gone.
The Fix: In your backtesting framework or custom trade execution wrapper, simulate a fixed or randomized latency buffer (e.g., 50ms–150ms). Instead of evaluating the tick at index $t$, evaluate the fill price at $t + \Delta t$ (where $\Delta t$ corresponds to your VPS-to-broker round-trip time).
Latency creates a time-lag between the trigger price ($P_{signal}$) and the execution price ($P_{exec}$). In fast-moving markets, a 30ms to 100ms round-trip delay means the price has already moved against you.
The Reality: MT5’s standard Strategy Tester assumes instant zero-latency execution by default. If your strategy relies on fast breakouts or micro-pullbacks, instant fills allow your backtest to capture liquidity that was already gone.
The Fix: In your backtesting framework or custom trade execution wrapper, simulate a fixed or randomized latency buffer (e.g., 50ms–150ms). Instead of evaluating the tick at index $t$, evaluate the fill price at $t + \Delta t$ (where $\Delta t$ corresponds to your VPS-to-broker round-trip time).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
2. Replacing Fixed Slippage with Dynamic Volatility Models
Static slippage settings in MT5 (e.g., setting a flat "2 points slippage") fail because slippage is non-linear. Slippage expands dramatically during high-impact news releases, market opens, liquidity vacuums, and when order size exceeds top-of-book depth.
A professional slippage model scales dynamically based on:
Current Spread Expansion: When spreads widen, market depth thins, increasing execution slippage.
Local Volatility (ATR / Recent Price Velocity): Rapid price changes correlate with higher queue position loss.
Order Volume Size: Larger lot sizes walk the book and suffer heavier price impact.
Static slippage settings in MT5 (e.g., setting a flat "2 points slippage") fail because slippage is non-linear. Slippage expands dramatically during high-impact news releases, market opens, liquidity vacuums, and when order size exceeds top-of-book depth.
A professional slippage model scales dynamically based on:
Current Spread Expansion: When spreads widen, market depth thins, increasing execution slippage.
Local Volatility (ATR / Recent Price Velocity): Rapid price changes correlate with higher queue position loss.
Order Volume Size: Larger lot sizes walk the book and suffer heavier price impact.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
3. MQL5 Implementation: Dynamic Slippage & Latency Wrapper
Here is how you can implement a dynamic slippage calculation function in MQL5 to guard your live execution and simulate realistic fills during custom backtests:
Here is how you can implement a dynamic slippage calculation function in MQL5 to guard your live execution and simulate realistic fills during custom backtests:
Code: Select all
//+------------------------------------------------------------------+
//| DynamicSlippageEngine.mqh |
//| Copyright 2026, Quant Systems |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property link "https://www.mql5.com"
#property version "1.00"
class CExecutionSimulator
private:
double m_base_latency_ms;
int m_atr_handle;
public:
CExecutionSimulator(double base_latency_ms = 75.0, int atr_period = 14)
{
m_base_latency_ms = base_latency_ms;
// Initialize ATR indicator handle for volatility scaling
m_atr_handle = iATR(_Symbol, _Period, atr_period);
}
~CExecutionSimulator()
{
IndicatorRelease(m_atr_handle);
}
// Calculate dynamic slippage in points based on current market state
uint GetDynamicSlippage(ENUM_ORDER_TYPE order_type)
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return 2; // Fallback default
// 1. Calculate current spread in points
double spread_points = (tick.ask - tick.bid) / _Point;
// 2. Get current ATR value for volatility weighting
double atr_values[];
ArraySetAsSeries(atr_values, true);
if(CopyBuffer(m_atr_handle, 0, 0, 1, atr_values) <= 0)
return (uint)MathMax(2.0, spread_points * 0.5);
double atr_points = atr_values[0] / _Point;
// 3. Dynamic Slippage Formula: Base slippage + spread factor + volatility factor
// Scalping volatile pairs (like gold or major crosses) requires higher dynamic weighting
double calculated_slippage = 1.0 + (spread_points * 0.25) + (atr_points * 0.05);
// Bound the slippage between reasonable limits (e.g., 1 to 30 points)
uint final_slippage = (uint)MathMin(MathMax(calculated_slippage, 1.0), 30.0);
return final_slippage;
}
// Simulate latency delay before order submission in live/forward testing
void SimulateLatencyDelay()
{
// Add a small jitter (e.g., +/- 25ms) to simulate real network fluctuations
double jitter = (MathRand() / 32767.0) * 50.0 - 25.0;
uint total_delay = (uint)MathMax(10.0, m_base_latency_ms + jitter);
Sleep(total_delay);
}
};Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
Best Practices for High-Frequency Backtesting in MT5
Use Every Tick Based on Real Ticks: Never backtest scalping strategies on "Open Prices Only" or 1-minute bar OHLC models. Always use "Every tick based on real ticks" in the MT5 Strategy Tester to ensure your stop-losses and take-profits are evaluated against genuine tick sequences.
Pessimistic Fill Assumptions: Configure your backtest testing profile with a variable latency delay script or add an explicit slippage penalty equal to 1.5x the average spread to stress-test your strategy's profit margin.
Colocate Your VPS: Ensure your live trading EA runs on a VPS physically located in the same data center as your broker's matching engine (e.g., Equinix LD4 in London for IC Markets) to compress baseline network latency down to sub-5ms levels.
Use Every Tick Based on Real Ticks: Never backtest scalping strategies on "Open Prices Only" or 1-minute bar OHLC models. Always use "Every tick based on real ticks" in the MT5 Strategy Tester to ensure your stop-losses and take-profits are evaluated against genuine tick sequences.
Pessimistic Fill Assumptions: Configure your backtest testing profile with a variable latency delay script or add an explicit slippage penalty equal to 1.5x the average spread to stress-test your strategy's profit margin.
Colocate Your VPS: Ensure your live trading EA runs on a VPS physically located in the same data center as your broker's matching engine (e.g., Equinix LD4 in London for IC Markets) to compress baseline network latency down to sub-5ms levels.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
Handling ECN execution anomalies—such as price changes, partial fills, and network disconnections—requires moving away from fire-and-forget OrderSend() calls. In an institutional ECN environment (like an MT5 raw spread setup), order execution is asynchronous, and a network ACK packet can be dropped even when the trade successfully executes on the matching engine.
To build production-grade MQL5 execution functions, your wrapper must handle idempotency, adaptive price deviation escalation, and partial fill volume tracking.
To build production-grade MQL5 execution functions, your wrapper must handle idempotency, adaptive price deviation escalation, and partial fill volume tracking.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
MQL5 Production Execution Wrapper (CTradeExecutor.mqh)
Below is a production-ready MQL5 class that handles ECN retcodes, retries with dynamic slippage expansion, partial fill tracking, and connection drop recovery.
Below is a production-ready MQL5 class that handles ECN retcodes, retries with dynamic slippage expansion, partial fill tracking, and connection drop recovery.
Code: Select all
//+------------------------------------------------------------------+
//| CTradeExecutor.mqh |
//| Copyright 2026, Quant Systems |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property link "https://www.mql5.com"
#property version "1.00"
#include <Trade\Trade.mqh>
class CTradeExecutor : public CTrade
private:
int m_max_retries;
uint m_base_delay_ms;
bool CheckConnection()
{
if(!TerminalInfoInteger(TERMINAL_CONNECTED) || !MQLInfoInteger(MQL_TRADE_ALLOWED))
{
Print("[Execution Error] Terminal disconnected or automated trading disabled.");
return false;
}
return true;
}
public:
CTradeExecutor(int max_retries=3, uint base_delay_ms=250)
{
m_max_retries = max_retries;
m_base_delay_ms = base_delay_ms;
}
~CTradeExecutor() {}
// Robust Order Send with ECN Error Handling & Partial Fill Management
bool ExecuteRobustOrder(MqlTradeRequest &request, MqlTradeResult &result)
{
int attempt = 0;
uint current_deviation = request.deviation;
while(attempt < m_max_retries)
{
// 1. Validate connection state before transmitting
if(!CheckConnection())
{
Sleep(1000 * (attempt + 1));
attempt++;
continue;
}
// 2. Transmit order to broker matching engine
ZeroMemory(result);
bool sent = OrderSend(request, result);
// 3. Handle Retcode Responses
if(sent)
{
switch(result.retcode)
{
case TRADE_RETCODE_DONE:
Print("Order executed successfully. Ticket: ", result.deal);
return true;
case TRADE_RETCODE_PARTIAL:
Print("Warning: Partial fill detected. Filled volume: ", result.volume);
// Adjust request volume for residual if necessary, or accept partial
return true;
case TRADE_RETCODE_PRICE_CHANGED:
case TRADE_RETCODE_REQUOTE:
case TRADE_RETCODE_OFF_QUOTES:
Print("Price shifted during transit. Retrying... Attempt: ", attempt + 1);
attempt++;
current_deviation += 5; // Expand slippage tolerance step-by-step
request.deviation = current_deviation;
// Refresh price for next attempt
request.price = (request.action == TRADE_ACTION_DEAL) ?
(request.type == ORDER_TYPE_BUY ? SymbolInfoDouble(request.symbol, SYMBOL_ASK) : SymbolInfoDouble(request.symbol, SYMBOL_BID)) :
request.price;
Sleep(m_base_delay_ms * attempt);
break;
case TRADE_RETCODE_CONNECTION:
case TRADE_RETCODE_TIMEOUT:
Print("Network timeout or connection drop. Querying history to verify fill status...");
if(VerifyOrphanedOrder(request))
return true; // Order made it through despite dropped ACK
attempt++;
Sleep(m_base_delay_ms * 2 * attempt);
break;
default:
Print("Critical trade rejection. Retcode: ", result.retcode, " Description: ", ResultRetcodeDescription(result.retcode));
return false;
}
}
else
{
uint err = GetLastError();
Print("OrderSend failed with system error: ", err);
attempt++;
Sleep(m_base_delay_ms * attempt);
}
}
Print("Max retry attempts reached. Order execution aborted.");
return false;
}
private:
// Prevents duplicate orders if a connection drops right after packet transmission (Idempotency check)
bool VerifyOrphanedOrder(const MqlTradeRequest &request)
{
// Check recent history (last 2 seconds) for orders matching magic number and comment
HistorySelect(TimeCurrent() - 5, TimeCurrent());
int total = HistoryDealsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket > 0)
{
if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == request.magic &&
HistoryDealGetString(ticket, DEAL_SYMBOL) == request.symbol)
{
Print("Orphaned order recovery: Found matching deal in history. Ticket: ", ticket);
return true;
}
}
}
return false;
}
};Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
Key Engineering Patterns Explained
Adaptive Slippage Escalation: When an ECN returns TRADE_RETCODE_PRICE_CHANGED, blindly hammering the server with the exact same price guarantees infinite loop rejections. Incrementing request.deviation by 2 to 5 points on each retry allows the EA to absorb minor market micro-velocity during high-frequency volatility spikes.
Orphaned Order Recovery (Idempotency): The most dangerous network drop happens after your packet reaches the broker's matching engine, but before the confirmation packet reaches your terminal. Checking HistoryDealsTotal() within a recovery verification loop prevents your EA from accidentally doubling down on a position after a perceived timeout.
Partial Fill Processing (TRADE_RETCODE_PARTIAL): On ECNs, if you submit a 5.0 lot order but top-of-book depth only has 2.0 lots available with ORDER_FILLING_IOC (Immediate or Cancel), the broker fills 2.0 and cancels the rest. Inspecting result.volume ensures your risk accounting matches reality.
Adaptive Slippage Escalation: When an ECN returns TRADE_RETCODE_PRICE_CHANGED, blindly hammering the server with the exact same price guarantees infinite loop rejections. Incrementing request.deviation by 2 to 5 points on each retry allows the EA to absorb minor market micro-velocity during high-frequency volatility spikes.
Orphaned Order Recovery (Idempotency): The most dangerous network drop happens after your packet reaches the broker's matching engine, but before the confirmation packet reaches your terminal. Checking HistoryDealsTotal() within a recovery verification loop prevents your EA from accidentally doubling down on a position after a perceived timeout.
Partial Fill Processing (TRADE_RETCODE_PARTIAL): On ECNs, if you submit a 5.0 lot order but top-of-book depth only has 2.0 lots available with ORDER_FILLING_IOC (Immediate or Cancel), the broker fills 2.0 and cancels the rest. Inspecting result.volume ensures your risk accounting matches reality.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
In high-frequency algorithmic trading and multi-symbol portfolio management, synchronous OrderSend() calls introduce a fatal bottleneck: thread blocking. When your EA executes OrderSend(), the main execution thread halts and waits for the broker's server round-trip ACK over TCP/IP. In a fast-moving market or when trading 10+ symbols concurrently, this blocking lag causes missed ticks, delayed stop-losses, and queuing starvation.
OrderSendAsync() solves this by operating as a non-blocking, asynchronous message dispatcher. It pushes the trade request into the MetaTrader terminal's outbound queue and returns control to your thread in microseconds, allowing your EA to continue processing incoming ticks instantaneously.
OrderSendAsync() solves this by operating as a non-blocking, asynchronous message dispatcher. It pushes the trade request into the MetaTrader terminal's outbound queue and returns control to your thread in microseconds, allowing your EA to continue processing incoming ticks instantaneously.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: How to handle execution latency and broker slippage?
The Asynchronous Architecture
Unlike synchronous execution where the return value of OrderSend() gives you the final trade result immediately, asynchronous execution splits the workflow into two distinct phases:
Dispatch Phase (OrderSendAsync): Submits the request to the terminal queue. It returns true almost instantly, providing a request_id in the MqlTradeResult struct.
Notification Phase (OnTradeTransaction): The terminal asynchronously fires the OnTradeTransaction() event handler when the broker confirms execution, rejects the order, or registers a deal/position update.
Unlike synchronous execution where the return value of OrderSend() gives you the final trade result immediately, asynchronous execution splits the workflow into two distinct phases:
Dispatch Phase (OrderSendAsync): Submits the request to the terminal queue. It returns true almost instantly, providing a request_id in the MqlTradeResult struct.
Notification Phase (OnTradeTransaction): The terminal asynchronously fires the OnTradeTransaction() event handler when the broker confirms execution, rejects the order, or registers a deal/position update.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.