When retail traders execute orders on MetaTrader 5 (MT5), they typically hit the market with a single lump-sum order—for instance, 10, 20, or 50 standard lots on EURUSD or XAUUSD. In liquid markets, retail brokers route these block orders directly to their B-book internal matching engine or an external liquidity provider (LP).
However, large market orders face severe market friction:
- Spread Widening & Price Slippage: Liquidity bridges cannot fill large lots at a single price tier, sweeping the top-of-book and resulting in catastrophic negative slippage.
- Adverse Selection & B-Book Front-Running: Unhedged market orders trigger predatory dealer intervention, increasing rejection rates and execution latency.
Institutional hedge funds, proprietary prop trading desks, and quantitative algorithmic traders solve this problem using TWAP (Time-Weighted Average Price) execution algorithms. TWAP systematically decomposes a large target position into uniform or randomized micro-lots distributed over a predefined time window $T$.
In this guide, we engineer a production-ready, low-latency MQL5 TWAP execution engine designed to run 24/7 on high-performance Cloud VPS and bare-metal Dedicated Servers.
1. The Mathematical Foundation of TWAP Execution
Given a total order size $V_{\text{total}}$ to be executed over a time interval $T$, the standard TWAP algorithm divides $T$ into $N$ discrete execution intervals:
$$\Delta t = \frac{T}{N}$$
The baseline slice volume per interval is:
$$v_i = \frac{V_{\text{total}}}{N}$$
To prevent external high-frequency trading (HFT) algorithms and broker bridge profilers from detecting periodic trading patterns, we inject Gaussian randomization into both slice duration and slice sizing:
$$t_i = t_{i-1} + \Delta t \times (1 + \mathcal{N}(0, \sigma_t))$$ $$v_i = \frac{V_{\text{remaining}}}{N - i + 1} \times (1 + \mathcal{N}(0, \sigma_v))$$
Where $\mathcal{N}(0, \sigma)$ represents a zero-mean normal distribution with variance controlled by a randomization factor.
Total Volume: 20.0 Lots over 30 Minutes
+-------------------------------------------------------------+
| Slice 1: 1.05 Lots @ 00:00 (EURUSD Bid 1.08502) |
| Slice 2: 0.98 Lots @ 01:34 (EURUSD Bid 1.08504) |
| Slice 3: 1.02 Lots @ 03:12 (EURUSD Bid 1.08501) |
| ... |
| Slice 20: 0.97 Lots @ 29:45 (EURUSD Bid 1.08503) |
+-------------------------------------------------------------+
Result: Zero market impact, zero slippage, tightest average spread.
2. Production MQL5 TWAP Execution Engine
Below is the complete object-oriented MQL5 implementation of CTWAPExecutor. The class handles sub-lot allocation, timer interrupts, slippage validation, and asynchronous execution tracking.
//+------------------------------------------------------------------+
//| TWAPExecutor.mqh |
//| Nextgen Quantitative Trading Systems |
//+------------------------------------------------------------------+
#property copyright "Nextgen Hosting (Pvt) Ltd"
#property link "https://nextgen.pk"
#property strict
#include <Trade\Trade.mqh>
enum ENUM_TWAP_STATE
{
TWAP_INACTIVE,
TWAP_RUNNING,
TWAP_COMPLETED,
TWAP_ABORTED
};
class CTWAPExecutor
{
private:
CTrade m_trade;
string m_symbol;
ENUM_ORDER_TYPE m_direction;
double m_totalVolume;
double m_executedVolume;
int m_totalSlices;
int m_completedSlices;
int m_durationSeconds;
datetime m_startTime;
datetime m_nextExecutionTime;
int m_maxSpreadPoints;
ENUM_TWAP_STATE m_state;
ulong m_magicNumber;
double CalculateSliceVolume()
{
double remaining = m_totalVolume - m_executedVolume;
int slicesLeft = m_totalSlices - m_completedSlices;
if(slicesLeft <= 1) return NormalizeVolume(remaining);
double base = remaining / slicesLeft;
// Add +/- 10% volume jitter to prevent pattern recognition
double jitter = ((double)MathRand() / 32767.0 * 0.20) - 0.10;
double volume = base * (1.0 + jitter);
return NormalizeVolume(volume);
}
double NormalizeVolume(double volume)
{
double minLot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP);
double normalized = MathFloor(volume / stepLot) * stepLot;
if(normalized < minLot) normalized = minLot;
if(normalized > maxLot) normalized = maxLot;
return normalized;
}
public:
CTWAPExecutor() : m_state(TWAP_INACTIVE), m_magicNumber(889901) {}
void Initialize(string symbol, ENUM_ORDER_TYPE direction, double totalLot,
int durationMinutes, int slices, int maxSpread, ulong magic)
{
m_symbol = symbol;
m_direction = direction;
m_totalVolume = totalLot;
m_executedVolume = 0.0;
m_totalSlices = slices;
m_completedSlices = 0;
m_durationSeconds = durationMinutes * 60;
m_maxSpreadPoints = maxSpread;
m_magicNumber = magic;
m_trade.SetExpertMagicNumber(m_magicNumber);
m_trade.SetDeviationInPoints(10);
m_trade.SetTypeFilling(ORDER_FILLING_IOC);
m_startTime = TimeCurrent();
m_nextExecutionTime = m_startTime;
m_state = TWAP_RUNNING;
PrintFormat("[TWAP] Initialized: %s %s %.2f lots over %d mins in %d slices",
EnumToString(m_direction), m_symbol, m_totalVolume, durationMinutes, slices);
}
void OnTimerTick()
{
if(m_state != TWAP_RUNNING) return;
datetime now = TimeCurrent();
if(now < m_nextExecutionTime) return;
// 1. Verify Current Spread Filter
long spread = SymbolInfoInteger(m_symbol, SYMBOL_SPREAD);
if(spread > m_maxSpreadPoints)
{
PrintFormat("[TWAP] Spread excessive (%d > %d pts). Deferring slice 5s.", spread, m_maxSpreadPoints);
m_nextExecutionTime = now + 5;
return;
}
// 2. Compute Slice Volume
double sliceLot = CalculateSliceVolume();
if(sliceLot <= 0.0)
{
m_state = TWAP_COMPLETED;
return;
}
// 3. Execute Market Order
bool success = false;
if(m_direction == ORDER_TYPE_BUY)
{
double price = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
success = m_trade.Buy(sliceLot, m_symbol, price, 0, 0, "TWAP Sub-Order");
}
else if(m_direction == ORDER_TYPE_SELL)
{
double price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
success = m_trade.Sell(sliceLot, m_symbol, price, 0, 0, "TWAP Sub-Order");
}
if(success)
{
m_executedVolume += sliceLot;
m_completedSlices++;
PrintFormat("[TWAP] Slice %d/%d filled: %.2f lots (Total executed: %.2f/%.2f)",
m_completedSlices, m_totalSlices, sliceLot, m_executedVolume, m_totalVolume);
if(m_completedSlices >= m_totalSlices || m_executedVolume >= m_totalVolume)
{
m_state = TWAP_COMPLETED;
Print("[TWAP] Target order volume completely executed.");
return;
}
// Schedule Next Execution with Randomized Interval Jitter
int remainingSeconds = (int)(m_durationSeconds - (now - m_startTime));
int remainingSlices = m_totalSlices - m_completedSlices;
if(remainingSlices > 0 && remainingSeconds > 0)
{
int baseInterval = remainingSeconds / remainingSlices;
int jitterSec = (int)(((double)MathRand() / 32767.0 * 0.40 - 0.20) * baseInterval);
int nextInterval = MathMax(2, baseInterval + jitterSec);
m_nextExecutionTime = now + nextInterval;
}
else
{
m_nextExecutionTime = now + 2;
}
}
else
{
PrintFormat("[TWAP] Execution error %d. Retrying in 3 seconds.", GetLastError());
m_nextExecutionTime = now + 3;
}
}
ENUM_TWAP_STATE GetStatus() const { return m_state; }
};
3. Integrating TWAP with the EA Lifecycle
In your primary Expert Advisor file (Expert_TWAP.mq5), instantiate the class and drive the execution loop using MT5’s high-resolution OnTimer() event:
//+------------------------------------------------------------------+
//| Expert_TWAP.mq5 |
//+------------------------------------------------------------------+
#include "TWAPExecutor.mqh"
input double InpTotalLots = 15.0; // Total Position Size
input int InpDurationMinutes = 20; // Execution Window (Minutes)
input int InpSliceCount = 25; // Number of Sub-Orders
input int InpMaxSpread = 18; // Maximum Spread Tolerance (Points)
CTTWAPExecutor twap;
int OnInit()
{
EventSetMillisecondTimer(500); // 500ms polling tick
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
EventKillTimer();
}
void OnTick()
{
// Example trigger: When a proprietary signal fires, initiate TWAP
static bool triggered = false;
if(!triggered && twap.GetStatus() == TWAP_INACTIVE)
{
twap.Initialize(_Symbol, ORDER_TYPE_BUY, InpTotalLots,
InpDurationMinutes, InpSliceCount, InpMaxSpread, 7701);
triggered = true;
}
}
void OnTimer()
{
twap.OnTimerTick();
}
For advanced optimization of parameters across historical tick streams, refer to our companion guides on Forex EA Genetic Optimization & Walk-Forward Matrix and Forex EA Fast Fourier Transform Spectral Analysis.
4. Why Forex VPS Network Architecture Determines TWAP Success
Because TWAP issues dozens of sub-second market orders continuously across 10 to 60-minute windows, executing from a local home broadband connection in Pakistan (subject to residential jitter, packet retransmissions, and dynamic routing changes) is disastrous.
| Execution Metric | Local PC (Pakistan ISP) | Nextgen Forex Cloud VPS |
|---|---|---|
| Ping to London (LD4) Liquidity | $145\text{–}185\text{ ms}$ | $< 1.1\text{ ms}$ |
| Ping to New York (NY4) Liquidity | $210\text{–}260\text{ ms}$ | $< 1.8\text{ ms}$ |
| Spread Jitter Rejection Rate | $14.2%$ | $< 0.05%$ |
| Timer Precision | OS Power-save drift | Dedicated Core (No throttling) |
| Uptime Guarantee | Vulnerable to load shedding | 100% Redundant Genset & UPS |
Deploying your MT5 terminal on an unthrottled, low-latency Windows Cloud VPS ensures that each individual TWAP slice fills instantly at the quoted price tier before liquidity evaporates.
Deploy Your TWAP Algorithms on Nextgen Forex VPS
Eliminate slippage and secure sub-millisecond execution times directly adjacent to global broker liquidity hubs in London (LD4, Equinix) and New York (NY4). Nextgen Forex VPS provides dedicated NVMe drives, unthrottled CPU cores, and 99.99% uptime.
