MQL5 中的高级订单执行算法:TWAP、VWAP 和冰山订单·进阶篇
TWAP 执行引擎的时间与手数逻辑
TWAP 拆单的核心在两段:计算下一笔触发时间,以及按区间投放手数。首区间用起始时间加初始延迟定位,后续区间则锚定 TimeCurrent() 往后推 intervalSeconds,且硬卡 m_endTime 不越界——这套写法能保证在 MT5 实盘里不会因重连或休眠导致节奏漂移。 Execute() 里先判 m_isActive 与 IsTimeToExecute(),未到点直接 return true 让定时器继续空转。手数方面,若开启随机化就走 GetRandomizedVolume(m_intervalVolume),否则吃固定 m_intervalVolume,并强制截断到 m_remainingVolume 以内,避免末段超卖。 下单走 MqlTradeRequest 直发,买价取 SYMBOL_ASK、卖价取 SYMBOL_BID,deviation 绑 m_slippage,magic 写死 123456 便于在账户历史里筛单。外汇与贵金属杠杆高、滑点跳空频繁,TWAP 只降低时机暴露,不消除方向性亏损可能,参数请在策略测试器先跑再上实盘。
if(m_currentInterval == class="num">0) { class=class="str">"cmt">// First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("TWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { class=class="str">"cmt">// For subsequent intervals, ensure proper spacing from current time class="type">class="kw">datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; class=class="str">"cmt">// Make sure we don&class="macro">#x27;t exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("TWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } class="kw">return nextTime; } class="type">bool CTWAP::Execute() { if(!m_isActive) class="kw">return false; class=class="str">"cmt">// Check if it&class="macro">#x27;s time to execute the next order if(!IsTimeToExecute()) class="kw">return true; class=class="str">"cmt">// Not time yet class=class="str">"cmt">// Calculate the volume for this execution class="type">class="kw">double volumeToExecute = m_useRandomization ? GetRandomizedVolume(m_intervalVolume) : m_intervalVolume; class=class="str">"cmt">// Ensure we don&class="macro">#x27;t exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; class=class="str">"cmt">// Get current market price class="type">class="kw">double price = class="num">0.0; if(m_orderType == ORDER_TYPE_BUY) price = SymbolInfoDouble(m_symbol, SYMBOL_ASK); else price = SymbolInfoDouble(m_symbol, SYMBOL_BID); Print("TWAP: Placing order for interval ", m_currentInterval, ", Volume: ", DoubleToString(volumeToExecute, class="num">2), ", Price: ", DoubleToString(price, _Digits)); class=class="str">"cmt">// Place the order using OrderSend directly for more control class="type">MqlTradeRequest request; class="type">MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = price; request.deviation = m_slippage; request.magic = class="num">123456; class=class="str">"cmt">// Magic number for identification class=class="str">"cmt">// Send the order class="type">bool success = OrderSend(request, result); if(!success) { Print("TWAP: OrderSend error: ", GetLastError()); class="kw">return false; } class=class="str">"cmt">// Check the result
◍ TWAP 执行后的状态更新与 VWAP 类骨架
TWAP 算法在单笔子单发送完成后,先判断 retcode 是否为 TRADE_RETCODE_DONE,非完成态直接打印错误码并返回 false,避免脏状态继续推进。 若成交成功,则累加 m_totalOrders 与 m_filledOrders,并把本次 volumeToExecute 计入 m_executedVolume,同时从 m_remainingVolume 扣减。m_currentInterval 自增、m_firstOrderPlaced 置 true,这是后续判断「是否首单已发」的依据。 下一笔触发时刻由 CalculateNextExecutionTime() 算出,前提是当前区间数小于总区间且剩余量大于 0;否则 m_isActive 置 false,代表拆分全部跑完或无量可跑。Print 里用 DoubleToString(volumeToExecute,2) 和 _Digits 精度输出成交量与价格,方便在 MT5 Experts 日志里核对每段执行痕迹。 VWAP 类 CVWAP 公有继承自 CExecutionAlgorithem,私有成员里 m_volumeProfile[] 与 m_intervalVolumes[] 承载历史成交量剖面,m_adaptiveMode 控制是否切到实时量调节,m_historyDays 决定剖面对齐几天历史,m_profileLoaded 标记剖面是否成功载入。 别把 TWAP 的状态机直接套给 VWAP 两套算法的剩余量扣减逻辑一致,但 VWAP 的每区间手数来自剖面权重而非均分;若复用 TWAP 的 m_nextExecutionTime 推算而不重载,会出现前密后疏的错拍。
if(result.retcode != TRADE_RETCODE_DONE) { Print("TWAP: OrderSend failed with code: ", result.retcode); class="kw">return false; } class=class="str">"cmt">// Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volumeToExecute; m_remainingVolume -= volumeToExecute; class=class="str">"cmt">// Update interval counter m_currentInterval++; m_firstOrderPlaced = true; class=class="str">"cmt">// Calculate the time for the next execution if(m_currentInterval < m_intervals && m_remainingVolume > class="num">0) m_nextExecutionTime = CalculateNextExecutionTime(); else m_isActive = false; class=class="str">"cmt">// All intervals completed or no volume left Print("TWAP: Executed ", DoubleToString(volumeToExecute, class="num">2), " at price ", DoubleToString(price, _Digits), ". Remaining: ", DoubleToString(m_remainingVolume, class="num">2), ", Next execution: ", TimeToString(m_nextExecutionTime)); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Volume-Weighted Average Price(VWAP) Algorithm | class=class="str">"cmt">//+------------------------------------------------------------------+ class CVWAP : class="kw">public CExecutionAlgorithm { class="kw">private: class="type">int m_intervals; class=class="str">"cmt">// Number of time intervals class="type">int m_currentInterval; class=class="str">"cmt">// Current interval class="type">class="kw">datetime m_nextExecutionTime; class=class="str">"cmt">// Next execution time class="type">class="kw">double m_volumeProfile[]; class=class="str">"cmt">// Historical volume profile class="type">class="kw">double m_intervalVolumes[]; class=class="str">"cmt">// Volume per interval based on profile class="type">bool m_adaptiveMode; class=class="str">"cmt">// Whether to adapt to real-time volume ENUM_ORDER_TYPE m_orderType; class=class="str">"cmt">// Order type(buy or sell) class="type">int m_historyDays; class=class="str">"cmt">// Number of days to analyze for volume profile class="type">bool m_profileLoaded; class=class="str">"cmt">// Flag indicating if profile was loaded class="type">bool m_firstOrderPlaced; class=class="str">"cmt">// Flag to track if first order has been placed class="type">int m_initialDelay; class=class="str">"cmt">// Initial delay in seconds before first execution class="type">class="kw">datetime m_lastCheckTime; class=class="str">"cmt">// Last time order status was checked class="type">int m_checkInterval; class=class="str">"cmt">// How often to check order status(seconds) class="kw">public: class=class="str">"cmt">// Constructor
「CVWAP 的执行节奏与冰山单骨架」
CVWAP 类的构造函数暴露了算法拆单的核心参数:默认回看 5 天历史成交、自适应模式开启、滑点 3 点、首单延迟 10 秒。把这些值直接丢进 MT5 策略测试器,能复现文末的时段切分逻辑。 CalculateNextExecutionTime 决定了母单在每个区间的触发点。首区间用 startTime + initialDelay 起步,之后每个区间按 (endTime-startTime)/intervals 的秒数顺推,且用 TimeCurrent() 做实时锚定,越界则钳制到 endTime。
CVWAP(class="type">class="kw">string symbol, class="type">class="kw">double volume, class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">int intervals, ENUM_ORDER_TYPE orderType, class="type">int historyDays = class="num">5, class="type">bool adaptiveMode = true, class="type">int slippage = class="num">3, class="type">int initialDelay = class="num">10); class=class="str">"cmt">// Implementation of class="kw">virtual methods class="kw">virtual class="type">bool Initialize() class="kw">override; class="kw">virtual class="type">bool Execute() class="kw">override; class="kw">virtual class="type">bool Update() class="kw">override; class="kw">virtual class="type">bool Terminate() class="kw">override; class=class="str">"cmt">// VWAP specific methods class="type">bool LoadVolumeProfile(); class="type">void CalculateIntervalVolumes(); class="type">void AdjustToRealTimeVolume(); class="type">class="kw">datetime CalculateNextExecutionTime(); class="type">class="kw">double GetCurrentVWAP(); class="type">bool IsTimeToExecute(); }; class="type">class="kw">datetime CVWAP::CalculateNextExecutionTime() { class=class="str">"cmt">// Calculate the duration of each interval class="type">int totalSeconds = (class="type">int)(m_endTime - m_startTime); class="type">int intervalSeconds = totalSeconds / m_intervals; class=class="str">"cmt">// Calculate the next execution time class="type">class="kw">datetime nextTime; if(m_currentInterval == class="num">0) { class=class="str">"cmt">// First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("VWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { class=class="str">"cmt">// For subsequent intervals, ensure proper spacing from current time class="type">class="kw">datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; class=class="str">"cmt">// Make sure we don&class="macro">#x27;t exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("VWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } class="kw">return nextTime; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iceberg Order Implementation | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIcebergOrder : class="kw">public CExecutionAlgorithm { class="kw">private: class="type">class="kw">double m_visibleVolume; class=class="str">"cmt">// Visible portion of the order class="type">class="kw">double m_minVisibleVolume; class=class="str">"cmt">// Minimum visible volume }
CVWAP(class="type">class="kw">string symbol, class="type">class="kw">double volume, class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">int intervals, ENUM_ORDER_TYPE orderType, class="type">int historyDays = class="num">5, class="type">bool adaptiveMode = true, class="type">int slippage = class="num">3, class="type">int initialDelay = class="num">10); class=class="str">"cmt">// Implementation of class="kw">virtual methods class="kw">virtual class="type">bool Initialize() class="kw">override; class="kw">virtual class="type">bool Execute() class="kw">override; class="kw">virtual class="type">bool Update() class="kw">override; class="kw">virtual class="type">bool Terminate() class="kw">override; class=class="str">"cmt">// VWAP specific methods class="type">bool LoadVolumeProfile(); class="type">void CalculateIntervalVolumes(); class="type">void AdjustToRealTimeVolume(); class="type">class="kw">datetime CalculateNextExecutionTime(); class="type">class="kw">double GetCurrentVWAP(); class="type">bool IsTimeToExecute(); }; class="type">class="kw">datetime CVWAP::CalculateNextExecutionTime() { class=class="str">"cmt">// Calculate the duration of each interval class="type">int totalSeconds = (class="type">int)(m_endTime - m_startTime); class="type">int intervalSeconds = totalSeconds / m_intervals; class=class="str">"cmt">// Calculate the next execution time class="type">class="kw">datetime nextTime; if(m_currentInterval == class="num">0) { class=class="str">"cmt">// First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("VWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { class=class="str">"cmt">// For subsequent intervals, ensure proper spacing from current time class="type">class="kw">datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; class=class="str">"cmt">// Make sure we don&class="macro">#x27;t exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("VWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } class="kw">return nextTime; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iceberg Order Implementation | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIcebergOrder : class="kw">public CExecutionAlgorithm { class="kw">private: class="type">class="kw">double m_visibleVolume; class=class="str">"cmt">// Visible portion of the order class="type">class="kw">double m_minVisibleVolume; class=class="str">"cmt">// Minimum visible volume
冰山订单类的成员与接口骨架
下面这段声明定义了一个冰山订单类(CIcebergOrder)的私有状态与公开接口,核心是把大单拆成可见小块挂出,降低对盘口的暴露。外汇与贵金属市场流动性瞬变,这类拆单逻辑只降低被追踪的概率,不保证成交价更优,实盘须自担滑点风险。 私有成员里,m_maxVisibleVolume 与 m_useRandomVisibleVolume 控制单笔可见上限与是否随机化;m_orderPlacementDelay 默认 1000 毫秒,即两次挂单最小间隔;m_avoidRoundNumbers 配合 m_priceDeviation(默认 2 点)把挂单价推离整数位。m_maxOrderLifetime 与 m_checkInterval 决定订单存活与轮询节奏,避免死单占坑。 公开构造器接收 symbol、volume、limitPrice、orderType,可见量默认在 0.0~0.0 间随机(useRandomVisibleVolume=true),avoidRoundNumbers 与 slippage=3 点均为出厂默认值,可直接在 MT5 里改参验证。 类还覆写 Initialize/Execute/Update/Terminate 四个虚函数,并暴露 GetRandomVisibleVolume、AdjustPriceToAvoidRoundNumbers、CheckAndReplaceOrder、IsOrderFilled 四个冰山专用方法——复制进 EA 框架后,重点看 CheckAndReplaceOrder 怎么在 m_maxOrderLifetime 超时后撤单重挂。
class="type">class="kw">double m_maxVisibleVolume; class=class="str">"cmt">// Maximum visible volume class="type">bool m_useRandomVisibleVolume; class=class="str">"cmt">// Whether to randomize visible volume class="type">int m_orderPlacementDelay; class=class="str">"cmt">// Delay between order placements(ms) class="type">bool m_avoidRoundNumbers; class=class="str">"cmt">// Whether to avoid round numbers in price class="type">class="kw">double m_limitPrice; class=class="str">"cmt">// Limit price for the orders class="type">ulong m_currentOrderTicket; class=class="str">"cmt">// Current active order ticket ENUM_ORDER_TYPE m_orderType; class=class="str">"cmt">// Order type(buy or sell) class="type">bool m_orderActive; class=class="str">"cmt">// Flag indicating if an order is currently active class="type">int m_priceDeviation; class=class="str">"cmt">// Price deviation to avoid round numbers(in points) class="type">class="kw">datetime m_lastCheckTime; class=class="str">"cmt">// Last time order status was checked class="type">int m_checkInterval; class=class="str">"cmt">// How often to check order status(seconds) class="type">int m_maxOrderLifetime; class=class="str">"cmt">// Maximum lifetime for an order in seconds class="type">class="kw">datetime m_orderPlacementTime; class=class="str">"cmt">// When the current order was placed class="kw">public: class=class="str">"cmt">// Constructor CIcebergOrder(class="type">class="kw">string symbol, class="type">class="kw">double volume, class="type">class="kw">double limitPrice, ENUM_ORDER_TYPE orderType, class="type">class="kw">double visibleVolume, class="type">class="kw">double minVisibleVolume = class="num">0.0, class="type">class="kw">double maxVisibleVolume = class="num">0.0, class="type">bool useRandomVisibleVolume = true, class="type">int orderPlacementDelay = class="num">1000, class="type">bool avoidRoundNumbers = true, class="type">int priceDeviation = class="num">2, class="type">int slippage = class="num">3); class=class="str">"cmt">// Implementation of class="kw">virtual methods class="kw">virtual class="type">bool Initialize() class="kw">override; class="kw">virtual class="type">bool Execute() class="kw">override; class="kw">virtual class="type">bool Update() class="kw">override; class="kw">virtual class="type">bool Terminate() class="kw">override; class=class="str">"cmt">// Iceberg specific methods class="type">class="kw">double GetRandomVisibleVolume(); class="type">class="kw">double AdjustPriceToAvoidRoundNumbers(class="type">class="kw">double price); class="type">bool CheckAndReplaceOrder(); class="type">bool IsOrderFilled(class="type">ulong ticket);
◍ 冰山单的挂单执行与状态自检
冰山算法下每一片碎单都不是盲目丢进市场的。Execute() 先判断 m_isActive,未激活直接返回 false 并打印提示,避免闲置状态下误触发挂单。 若 m_orderActive 已为真,说明上一片单子还挂着,这时不新开仓,而是走 CheckAndReplaceOrder() 去查状态、必要时替换,保证同一时刻只有一片可见挂单在跑。 可见手算有两种来源:开了随机可见量就取 GetRandomVisibleVolume(),否则用固定的 m_visibleVolume。代码里有一道硬约束——if(volumeToExecute > m_remainingVolume) 就截成剩余量,防止最后一片单超额。 下单走 MqlTradeRequest 直控:action 设 TRADE_ACTION_PENDING,magic 写死 123456 便于在 MT5 成交历史里筛冰山单。OrderSend 后分两层校验,success 为假看 GetLastError(),result.retcode 不等于 TRADE_RETCODE_DONE 也判失败。 过了校验才把 result.order 存进 m_currentOrderTicket,m_orderActive 置真,并用 TimeCurrent() 记挂单时间。外汇与贵金属杠杆高,碎单节奏失控可能放大滑点风险,参数请在模拟盘先验证。
class="type">bool IsOrderPartiallyFilled(class="type">ulong ticket, class="type">class="kw">double &filledVolume); class="type">bool IsOrderCancelled(class="type">ulong ticket); class="type">bool IsOrderExpired(class="type">ulong ticket); class="type">bool IsOrderTimeout(); class="type">ulong GetCurrentOrderTicket() { class="kw">return m_currentOrderTicket; } class="type">bool IsOrderActive() { class="kw">return m_orderActive; } }; class="type">bool CIcebergOrder::Execute() { if(!m_isActive) { Print("Iceberg: Execute called but algorithm is not active"); class="kw">return false; } class=class="str">"cmt">// If an order is already active, check its status if(m_orderActive) { Print("Iceberg: Execute called with active order ", m_currentOrderTicket); class="kw">return CheckAndReplaceOrder(); } class=class="str">"cmt">// Calculate the volume for this execution class="type">class="kw">double volumeToExecute = m_useRandomVisibleVolume ? GetRandomVisibleVolume() : m_visibleVolume; class=class="str">"cmt">// Ensure we don&class="macro">#x27;t exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; Print("Iceberg: Placing order for ", DoubleToString(volumeToExecute, class="num">2), " at price ", DoubleToString(m_limitPrice, _Digits)); class=class="str">"cmt">// Place the order using OrderSend directly for more control class="type">MqlTradeRequest request; class="type">MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_PENDING; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = m_limitPrice; request.deviation = m_slippage; request.magic = class="num">123456; class=class="str">"cmt">// Magic number for identification class=class="str">"cmt">// Send the order class="type">bool success = OrderSend(request, result); if(!success) { Print("Iceberg: OrderSend error: ", GetLastError()); class="kw">return false; } class=class="str">"cmt">// Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("Iceberg: OrderSend failed with code: ", result.retcode); class="kw">return false; } class=class="str">"cmt">// Store the order ticket m_currentOrderTicket = result.order; m_orderActive = true; m_orderPlacementTime = TimeCurrent(); Print("Iceberg: Order placed successfully. Ticket: ", m_currentOrderTicket, ", Volume: ", DoubleToString(volumeToExecute, class="num">2),
「冰山单的存活巡检与超时换单」
Update() 是冰山单算法在 MT5 里持续活着的核心。它先判断 m_isActive 是否为真,若算法已停止就直接返回 false 并打印提示,避免无意义的轮询消耗。 每次进入检查前会用 TimeCurrent() 对比 m_lastCheckTime + m_checkInterval,只有过了设定间隔才真正干活。这个间隔你在实盘里建议设在 500~2000 毫秒,间隔太短会被经纪商认定为高频行为,外汇与贵金属品种的高风险在于点差跳变可能让限价单长期不成交。 若 m_remainingVolume <= 0,说明碎单已全部吃完,调用 Terminate() 收尾。否则打印当前 Bid / Ask / 限价,用 SymbolInfoDouble 抓实时报价,DoubleToString 按 _Digits 精度输出,方便你直接在专家日志里核对挂单位置。 当 m_orderActive 为真且 IsOrderTimeout() 触发,算法会取消旧单、清空 ticket、Sleep(m_orderPlacementDelay) 后调 Execute() 重挂。没有活跃单时则直接 Execute() 开新碎单。把 m_orderPlacementDelay 调成 100~300 毫秒,能降低在同一价位被扫多次的概率。
class="type">bool CIcebergOrder::Update() { if(!m_isActive) { Print("Iceberg: Update called but algorithm is not active"); class="kw">return false; } class=class="str">"cmt">// Check if all volume has been executed if(m_remainingVolume <= class="num">0) { Print("Iceberg: All volume executed. Terminating algorithm."); class="kw">return Terminate(); } class=class="str">"cmt">// Check if it&class="macro">#x27;s time to check order status class="type">class="kw">datetime currentTime = TimeCurrent(); if(currentTime >= m_lastCheckTime + m_checkInterval) { m_lastCheckTime = currentTime; class=class="str">"cmt">// Log current market conditions class="type">class="kw">double currentBid = SymbolInfoDouble(m_symbol, SYMBOL_BID); class="type">class="kw">double currentAsk = SymbolInfoDouble(m_symbol, SYMBOL_ASK); Print("Iceberg: Market update - Bid: ", DoubleToString(currentBid, _Digits), ", Ask: ", DoubleToString(currentAsk, _Digits), ", Limit Price: ", DoubleToString(m_limitPrice, _Digits)); class=class="str">"cmt">// If an order is active, check its status if(m_orderActive) { class=class="str">"cmt">// Check if the order has been active too class="type">long if(IsOrderTimeout()) { Print("Iceberg: Order ", m_currentOrderTicket, " has timed out. Replacing it."); class=class="str">"cmt">// Cancel the current order if(!CancelOrder(m_currentOrderTicket)) { Print("Iceberg: Failed to cancel timed out order ", m_currentOrderTicket); } class=class="str">"cmt">// Reset order tracking m_orderActive = false; m_currentOrderTicket = class="num">0; class=class="str">"cmt">// Place a new order after a delay Sleep(m_orderPlacementDelay); class="kw">return Execute(); } class="kw">return CheckAndReplaceOrder(); } else { class=class="str">"cmt">// If no order is active, execute a new one Print("Iceberg: No active order, executing new order"); class="kw">return Execute(); } } }