MQL5 中的高级订单执行算法:TWAP、VWAP 和冰山订单(基础篇)
MT5里三类执行算法的真身
在 MetaTrader 5 的 MQL5 环境里,TWAP(时间加权均价)、VWAP(成交量加权均价)和冰山订单并不是经纪商默认给零售户的按钮,而是需要用代码自己搭的执行算法。它们的共同目的,是把一笔大单拆开,降低对盘口的冲击,但拆法逻辑完全不同:TWAP 按固定时间间隔均匀发单,VWAP 盯着成交量分布发单,冰山则只露出一个小头寸、其余挂藏单。 原文给出的系列框架里,这一节先点出后续要落地的四个模块:MQL5 基础实现、性能分析器(Profiler)跑法、算法间横向比较、以及把执行算法缝进交易策略的回测示例。该系列发布于 2026 年 1 月 16 日,原文标注阅读量 902、评论 6,属于刚铺开的技术向内容,还没到收益结论阶段。 对外汇与贵金属交易者而言,这类算法在低点差主流品种上意义有限,在流动性薄、滑点大的交叉盘或异常时段才可能显出优势;自行部署有代码风险与执行失效风险,务必先在策略测试器里用历史数据验证。
「散户也能用的机构级执行思路」
机构用了几十年的算法拆单,核心目的只有一个:避开自己的单子把价格打歪。市价单一把梭,哪怕在欧美盘流动性最好的时段,也会因为订单簿被瞬间抽空而产生额外滑点,这种拖累在震荡市里更明显。 MQL5 把这套逻辑下放到了个人桌面。你不需要管账户里是 0.5 手还是 5 手,波动性对执行的侵蚀都存在;用 TWAP、VWAP、Iceberg 这类策略把大单拆小、错时投放,滑点倾向被压低,平均成交价可能比一次性介入更优。 冰山单(Iceberg)只挂出真实量的零头,剩下藏在后台,盯着盘口的嗅单算法很难猜出你的全貌。这套东西以前是百万预算团队的专利,现在写几段 MQL5 接进 MT5 就能跑。外汇与贵金属杠杆高、波动急,拆单只降低冲击概率,不消除方向风险。
◍ TWAP、VWAP 与冰山单的实战适用边界
大单拆细不是装饰性操作,核心目标是把市场冲击摊薄、把滑点压低。MT5 自营算法或第三方 EA 里最常见的三种基础执行逻辑,各自对应不同的流动性环境,选错场景比不拆更伤。 TWAP 把总单量按固定时间间隔切成等份发出,全程不盯价格波动,只在设定起止时间内匀速落地。它适合流动性平稳、你只求某时间段平均成交价的场景;若硬套在亚盘尾段流动性骤降的 XAUUSD,固定节奏反而容易在稀疏盘口吃大滑点。外汇与贵金属杠杆高、跳空频繁,这类机械节奏需手动避开数据窗。 VWAP 比 TWAP 多一层成交量加权:依据历史成交量曲线分配每笔大小,量大的时段多跑、量小的时段少跑,部分实现还能读实时量能微调。EURUSD 日内量能呈双峰(伦敦+纽约重叠段明显放大),顺这个分布挂单,冲击成本倾向低于均分法。 冰山单走另一条路——只露尖角,底层挂单隐藏。每成交一小块再放下一小块,可见量可固定也可随机抖动以防被嗅探;通常用限价单保价格。在流动性薄的 cross 货币对或夜盘贵金属里,想在不惊动盘口的前提下出掉大仓,这是更稳的隐蔽通道,但成交耗时不可控,需接受可能拖过你预期窗口。
把执行算法写成可复用的 MQL5 类
做拆单执行系统,先别急着堆策略,把共性抽进一个基类最省事。CExecutionAlgorithm 管的是全算法都要用的状态:总成交量、已成交、剩余量、起止时间、滑点、成交均价。派生类只重写自己的下单节奏,基类负责记账和发单。 PlaceOrder 是基类的核心发单函数。它对 MqlTradeRequest 和 MqlTradeResult 做零初始化,填交易品种、成交量、订单类型、价格、滑点和幻数,然后调 OrderSend 发市价单。若发送失败或经纪商返回码不是 TRADE_RETCODE_DONE,记错误并返回 false;成功才更新总单/已成单计数、已执行与剩余量,重算均价并拿单号返回 true。 TWAP 把 m_startTime 到 m_endTime 切成 m_intervals 个等长段。CalculateNextExecutionTime 首调返回 m_startTime + m_initialDelay,后续返回 TimeCurrent() + 一个间隔秒数,且绝不越过 m_endTime。Execute 到点从剩余量切固定或随机小块,查卖价(买)或买价(卖),发完单推进间隔计数、标首单已发,并排下一次函数或在区间/量用完时停策略。 VWAP 与 TWAP 结构相近,但按历史成交量分布配权重,不是均分。冰山单走挂单路线:Execute 用 TRADE_ACTION_PENDING 只挂可见切片,成功存单号并标活动;Update 按 m_checkInterval 轮询,超时则撤单、休眠 m_orderPlacementDelay 再补新切片。外汇与贵金属杠杆高,这类算法只降市场冲击,不消除价格反向风险,参数请在策略测试器用真实点差验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base class for all execution algorithms | class=class="str">"cmt">//+------------------------------------------------------------------+ class CExecutionAlgorithm { class="kw">protected: class="type">class="kw">string m_symbol; class=class="str">"cmt">// Symbol to trade class="type">class="kw">double m_totalVolume; class=class="str">"cmt">// Total volume to execute class="type">class="kw">double m_executedVolume; class=class="str">"cmt">// Volume already executed class="type">class="kw">double m_remainingVolume; class=class="str">"cmt">// Volume remaining to execute class="type">class="kw">datetime m_startTime; class=class="str">"cmt">// Start time for execution class="type">class="kw">datetime m_endTime; class=class="str">"cmt">// End time for execution class="type">bool m_isActive; class=class="str">"cmt">// Flag indicating if the algorithm is active class="type">int m_totalOrders; class=class="str">"cmt">// Total number of orders placed class="type">int m_filledOrders; class=class="str">"cmt">// Number of filled orders class="type">class="kw">double m_avgExecutionPrice; class=class="str">"cmt">// Average execution price class="type">class="kw">double m_executionValue; class=class="str">"cmt">// Total value of executed orders class="type">int m_slippage; class=class="str">"cmt">// Allowed slippage in points class="kw">public: class=class="str">"cmt">// Constructor CExecutionAlgorithm(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 slippage = class="num">3); class=class="str">"cmt">// Destructor class="kw">virtual ~CExecutionAlgorithm(); class=class="str">"cmt">// Common methods class="kw">virtual class="type">bool Initialize();
「执行算法类的接口与下单实现」
在 MT5 的自定义执行算法里,基类的纯虚函数 Execute() 和 Update() 强制子类给出具体逻辑,而 Terminate() 给了默认空实现,意味着你可以只重写需要介入的环节。GetSymbol、GetTotalVolume 这类 const 内联 getter 直接返回成员变量,调用开销极低,适合在每tick里频繁读状态。 下单核心落在 PlaceOrder(),它封装了 MqlTradeRequest 的填充与 OrderSend 调用。注意 request.magic 写死为 123456,这只是示例用的识别码,实盘里建议改成随策略实例变化的动态值,否则多 EA 同时跑会分不清成交归属。 每次成功成交后,m_totalOrders 与 m_filledOrders 都自增,m_executedVolume 累加、m_remainingVolume 扣减,并调用 UpdateAverageExecutionPrice 刷新均价。若 OrderSend 返回 false 或 retcode 不等于 TRADE_RETCODE_DONE,会 Print 错误码并退出,不更新统计——这套计数逻辑让你在回测或实盘里能直接读出算法吃单的完成度。外汇与贵金属杠杆高,滑点字段 m_slippage 设宽了容易成交在劣价,设窄了可能连单都丢,开 MT5 把这段贴进类里改 magic 和 deviation 跑一遍就知道了。
class="kw">virtual class="type">bool Execute() = class="num">0; class="kw">virtual class="type">bool Update() = class="num">0; class="kw">virtual class="type">bool Terminate(); class=class="str">"cmt">// Utility methods class="type">bool PlaceOrder(ENUM_ORDER_TYPE orderType, class="type">class="kw">double volume, class="type">class="kw">double price); class="type">bool CancelOrder(class="type">ulong ticket); class="type">void UpdateAverageExecutionPrice(class="type">class="kw">double price, class="type">class="kw">double volume); class=class="str">"cmt">// Getters class="type">class="kw">string GetSymbol() const { class="kw">return m_symbol; } class="type">class="kw">double GetTotalVolume() const { class="kw">return m_totalVolume; } class="type">class="kw">double GetExecutedVolume() const { class="kw">return m_executedVolume; } class="type">class="kw">double GetRemainingVolume() const { class="kw">return m_remainingVolume; } class="type">class="kw">datetime GetStartTime() const { class="kw">return m_startTime; } class="type">class="kw">datetime GetEndTime() const { class="kw">return m_endTime; } class="type">bool IsActive() const { class="kw">return m_isActive; } class="type">int GetTotalOrders() const { class="kw">return m_totalOrders; } class="type">int GetFilledOrders() const { class="kw">return m_filledOrders; } class="type">class="kw">double GetAverageExecutionPrice() const { class="kw">return m_avgExecutionPrice; } }; class="type">bool CExecutionAlgorithm::PlaceOrder(ENUM_ORDER_TYPE orderType, class="type">class="kw">double volume, class="type">class="kw">double price) { class=class="str">"cmt">// Prepare the trade request class="type">MqlTradeRequest request; class="type">MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volume; request.type = 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("OrderSend error: ", GetLastError()); class="kw">return false; } class=class="str">"cmt">// Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("OrderSend failed with code: ", result.retcode); class="kw">return false; } class=class="str">"cmt">// Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volume; m_remainingVolume -= volume; UpdateAverageExecutionPrice(price, volume); class=class="str">"cmt">// Store the order ticket for future reference class="type">ulong ticket = result.order;
◍ TWAP 类把时间切片拆成等权成交
CTWAP 继承自 CExecutionAlgorithm,核心思路是把总成交量按时间均分到 m_intervals 个区间,每个区间发 m_intervalVolume 的手数。构造函数里默认 initialDelay=10 秒,意味着算法启动后不会立刻吃单,先等 10 秒再看盘口。 成员变量里 m_useRandomization 和 m_randomizationFactor(默认 0.2)决定每笔是否做 ±20% 的量级抖动,用来降低被识别为机械单的概率。m_checkInterval 控制轮询成交状态的频率,避免每 tick 都去查订单拖慢 EA。 CalculateNextExecutionTime 先算 totalSeconds = m_endTime - m_startTime,再除 m_intervals 得到 intervalSeconds。这套切法在流动性平的时段能把冲击成本摊薄,但外汇与贵金属杠杆高、滑点跳变快,实际执行偏离 TWAP 理论价的可能不小,回测和实盘都建议先开 MT5 用Tick数据跑一遍。
class CTWAP : 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_intervalVolume; class=class="str">"cmt">// Volume per interval class="type">bool m_useRandomization; class=class="str">"cmt">// Whether to randomize order sizes class="type">class="kw">double m_randomizationFactor; class=class="str">"cmt">// Factor for randomization(class="num">0-class="num">1) ENUM_ORDER_TYPE m_orderType; class=class="str">"cmt">// Order type(buy or sell) 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 CTWAP(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">bool useRandomization = false, class="type">class="kw">double randomizationFactor = class="num">0.2, 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">// TWAP specific methods class="type">void CalculateIntervalVolume(); class="type">class="kw">datetime CalculateNextExecutionTime(); class="type">class="kw">double GetRandomizedVolume(class="type">class="kw">double baseVolume); class="type">bool IsTimeToExecute(); }; class="type">class="kw">datetime CTWAP::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;