MQL5 中的高级订单执行算法:TWAP、VWAP 和冰山订单·综合运用
📘

MQL5 中的高级订单执行算法:TWAP、VWAP 和冰山订单·综合运用

第 3/3 篇

◍ 函数收尾的返回逻辑

在 MQL5 自定义函数里,末尾一句 return true; 承担的是状态反馈职责,告诉调用方本次执行未触发异常分支。 从已给出的代码片段看,它处于函数闭合大括号之前,说明前面大概率有若干条件判断,只有全部通过才会落到这一行。 你在 MT5 里写指标或 EA 的辅助函数时,可以照这个结构:异常提前 return false,正常路径末尾 return true,便于上层用 if 直接接结果。 外汇与贵金属行情跳空频繁,这类布尔返回值能帮你快速隔离掉无效 tick,降低误算概率。

MQL5 / C++
  class="kw">return true;
}

「把算法执行质量拆成可量化指标」

做执行算法最怕凭感觉说“单子填得还行”。CPerformanceAnalyzer 的思路是把决策价、成交流、时间戳全收进一个类里,构建时传入品种和决策基准价,开始时间自动记为当前时刻,后续每一笔子单成交都喂给 RecordExecution(time, price, volume)。 它跑完一轮后由 CalculateMetrics() 算出几个硬指标:执行成本缺口(决策价与真实成交成本之间的损益差)、相对报价的平均滑点、估计的市场影响、总执行耗时(结束减开始),以及相比基准的价格改善幅度。外汇与贵金属点差跳变频繁,这些数字直接决定策略是否值得上实盘,属高风险验证项。 想横向比策略,用 CompareAlgorithms(otherAnalyzer) 把两次运行放一起看谁更优;PrintReport() 丢日志里速览,SaveReportToFile(filename) 落盘留档。轻量 getter 把每个指标暴露出来,接自定义仪表板或继续分析都行。 下面这段是类骨架,私有成员存符号、起止时间、决策价与各项累计指标,公有方法覆盖记录、计算、对比和报告。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Performance Analyzer for Execution Algorithms                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPerformanceAnalyzer
{
class="kw">private:
   class="type">class="kw">string            m_symbol;            class=class="str">"cmt">// Symbol being analyzed
   class="type">class="kw">datetime          m_startTime;         class=class="str">"cmt">// Analysis start time
   class="type">class="kw">datetime          m_endTime;           class=class="str">"cmt">// Analysis end time
   class="type">class="kw">double            m_decisionPrice;     class=class="str">"cmt">// Price at decision time
   class="type">class="kw">double            m_avgExecutionPrice; class=class="str">"cmt">// Average execution price
   class="type">class="kw">double            m_totalVolume;       class=class="str">"cmt">// Total volume executed
   class="type">class="kw">double            m_implementationShortfall; class=class="str">"cmt">// Implementation shortfall
   class="type">class="kw">double            m_marketImpact;      class=class="str">"cmt">// Estimated market impact
   class="type">class="kw">double            m_slippage;          class=class="str">"cmt">// Average slippage
   class="type">int               m_executionTime;     class=class="str">"cmt">// Total execution time in seconds
   class="type">class="kw">double            m_priceImprovement;  class=class="str">"cmt">// Total price improvement

class="kw">public:
   class=class="str">"cmt">// Constructor
   CPerformanceAnalyzer(class="type">class="kw">string symbol, class="type">class="kw">double decisionPrice);

   class=class="str">"cmt">// Analysis methods
   class="type">void              RecordExecution(class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">class="kw">double volume);
   class="type">void              CalculateMetrics();
   class="type">void              CompareAlgorithms(CPerformanceAnalyzer &other);

   class=class="str">"cmt">// Reporting methods
   class="type">void              PrintReport();
   class="type">void              SaveReportToFile(class="type">class="kw">string filename);

   class=class="str">"cmt">// Getters
   class="type">class="kw">double            GetImplementationShortfall() const { class="kw">return m_implementationShortfall; }
   class="type">class="kw">double            GetMarketImpact() const { class="kw">return m_marketImpact; }
   class="type">class="kw">double            GetSlippage() const { class="kw">return m_slippage; }
   class="type">int               GetExecutionTime() const { class="kw">return m_executionTime; }

从类里抠出成交优化幅度

MQL5 里把成交优化幅度封装成只读成员函数是很常见的做法,上面这段就是典型实现:用 const 限定符保证调用时不改写对象状态,直接返回私有变量 m_priceImprovement。 这个 double 值一般是回测或实盘里「实际成交价相对基准价的偏移量」,单位与品种报价精度一致。你可以在 EA 初始化后打印一次,验证该值是否被正确赋值,避免后续逻辑误用零值。 如果 m_priceImprovement 长期为 0,说明成交优化模块可能没接进去;外汇与贵金属杠杆高、滑点风险大,该字段异常会直接影响策略假设的可靠性,建议开 MT5 用样例品种跑一遍观察数值分布。

MQL5 / C++
class="type">class="kw">double GetPriceImprovement() const { class="kw">return m_priceImprovement; }

◍ 三种执行算法的适用边界

在 MT5 的算法执行框架里,没有哪一种策略能通吃所有行情。TWAP 把委托按等时间切片抛出,逻辑简单且成交节奏可预期,但在伦敦盘突发行情里它不会减速,容易在波动段吃掉不利价差。 VWAP 依赖历史成交量曲线来安排下单权重,顺着真实吞吐节奏走,在亚盘黄金横盘、成交量钟形分布清晰的时段,均价通常优于 TWAP;代价是要先备好历史 tick 量数据,代码层比 TWAP 重。 冰山订单把大单藏成小额挂单,适合流动性薄的交叉盘或贵金属止损密集区,能把市场冲击压到最低;但剩余量什么时候吃完说不准,碰到对手撤单可能只成交一半。外汇与贵金属杠杆高,选错算法会放大滑点风险,建议先开 MT5 策略测试器用过去 30 天数据跑一遍三种模式的成交分布再定。

「用执行管理器把算法接进策略」

把 TWAP、VWAP、冰山单这些拆单逻辑真正用起来,关键不在于算法本身,而在于有没有一层统一的调度面。直接在各处调算法对象,代码会迅速腐化成状态机迷宫;加一个 facade 层,交易系统只跟管理器对话,算法内部怎么切片、怎么追历史成交量,对外全不可见。 CExecutionManager 就是这么个外立面:内部只握两个指针,一个指向当前选定的 CExecutionAlgorithm(可能是 TWAP/VWAP/冰山任一),另一个指向 CPerformanceAnalyzer 做执行回溯。创建时调 CreateTWAP / CreateVWAP / CreateIcebergOrder,把品种、总手数、起止时间、间隔数、订单类型以及算法特有参数(随机化系数默认 0.2、VWAP 回看 5 日历史、冰山单默认延迟 1000 毫秒等)一次性灌进去。 生命周期是四步闭环:Initialize() 建状态,Execute() 触发下一片子单,Update() 刷成交流、行情与超时,Terminate() 收尾或手动停。若 EnablePerformanceAnalysis() 开了,管理器会按决策基准记执行价,PrintPerformanceReport() 直接吐滑点和损益简报。想自己探算法内部,GetAlgorithm() 拿裸对象即可。 外汇与贵金属杠杆高、滑点突变频繁,这套管理器最大的实用价值是:你能在不碰策略核心的情况下,把同一种信号分别跑 TWAP 和冰山,对比两者在 EURUSD 美盘开盘前后 30 分钟的滑点分布,大概率能看出隐藏单在流动性薄时更抗冲击。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Execution Manager - Facade for all execution algorithms           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CExecutionManager
{
class="kw">private:
  CExecutionAlgorithm* m_algorithm;        class=class="str">"cmt">// 当前选用的执行算法指针(TWAP/VWAP/冰山)
  CPerformanceAnalyzer* m_analyzer;        class=class="str">"cmt">// 绩效分析器指针,跟踪执行质量

class="kw">public:
  class=class="str">"cmt">// 构造函数
  CExecutionManager();

  class=class="str">"cmt">// 析构函数
  ~CExecutionManager();

  class=class="str">"cmt">// 算法创建方法
  class="type">bool                CreateTWAP(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">bool                CreateVWAP(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">bool                CreateIcebergOrder(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">// 执行方法
  class="type">bool                Initialize();

把执行类接口拆开看

这段声明定义了一个交易执行对象的对外方法群,核心围绕下单、更新、终止三条生命周期函数。Execute 返回 bool,意味着调用方必须处理发单失败的情况,而不是假设一定成交。 Update 与 Terminate 同样返回 bool,说明挂单状态刷新和策略中止都可能在运行时被拒绝——比如品种休市或保证金不足。把返回值当摆设,是 EA 在实盘里静默失控的常见根源。 下方 EnablePerformanceAnalysis 接收一个 decisionPrice 双精度参数,用来标定绩效统计的基准价;PrintPerformanceReport 则负责把分析结果输出到日志。外汇与贵金属杠杆高,这类基准价设错会让回测报告完全失真,上线前应在 MT5 策略测试器里用真实点差复验。 GetAlgorithm 直接回传内部算法指针,调用层可借此读取底层逻辑状态,但也要注意不要跨线程乱改成员。

MQL5 / C++
  class="type">bool                Execute();
  class="type">bool                Update();
  class="type">bool                Terminate();

  class=class="str">"cmt">// Performance analysis
  class="type">void                EnablePerformanceAnalysis(class="type">class="kw">double decisionPrice);
  class="type">void                PrintPerformanceReport();

  class=class="str">"cmt">// Getters
  CExecutionAlgorithm* GetAlgorithm() { class="kw">return m_algorithm; }
};

◍ 把执行算法塞进信号逻辑的三类拼法

把执行管理器接进策略,核心就一套骨架:用静态布尔量 inPosition 标记是否在手,用跨 tick 存活的 CExecutionManager 托管算法生命周期。信号一来就调对应的 Create 方法(TWAP / VWAP / Iceberg),把品种、总手数、时间窗或限价、切片数、订单方向丢进去,成功就 Initialize() 并置位。 持仓期间每个 OnTick 都跑 executionManager.Update(),内部自己轮询成交、处理超时、排下一刀切片;冰山单则是补子单。算法不再活跃(IsActive() 回 false)就清标志收工。VWAP 均值回归多一道保险:执行中途 RSI 反向破阈就 Terminate() 提前撤剩余切片。 三个现成拼法:趋势跟踪挂 TWAP,快 MA(20) 上穿慢 MA(50) 后,把 1 手在 1 小时内切 5 等份买入;均值回归挂 VWAP,RSI<30 买、>70 卖,同样 1 小时按历史量切 5 份,反转即终止;突破挂冰山,破通道后挂限价,每次露 0.1 手补到满 1 手。 换算法只需替换 CreateTWAP / CreateVWAP / CreateIcebergOrder,信号层一行不用改。下面这段是趋势+ TWAP 的裸 OnTick,注意 maPeriodFast=20、maPeriodSlow=50、executionIntervals=5、时间窗写死 3600 秒,开 MT5 新建 EA 粘进去就能跑。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trend-Following Strategy with TWAP Execution                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
  class=class="str">"cmt">// Strategy parameters
  class="type">int maPeriodFast = class="num">20;
  class="type">int maPeriodSlow = class="num">50;
  class="type">class="kw">double volume = class="num">1.0;
  class="type">int executionIntervals = class="num">5;
  
  class=class="str">"cmt">// Calculate indicators
  class="type">class="kw">double maFast = iMA(Symbol(), PERIOD_CURRENT, maPeriodFast, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0);
  class="type">class="kw">double maSlow = iMA(Symbol(), PERIOD_CURRENT, maPeriodSlow, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0);
  
  class=class="str">"cmt">// Check for entry conditions
  class="kw">static class="type">bool inPosition = false;
  class="kw">static CExecutionManager executionManager;
  
  if(!inPosition)
  {
      class=class="str">"cmt">// Buy signal: Fast MA crosses above Slow MA
      if(maFast > maSlow)
      {
          class=class="str">"cmt">// Create TWAP execution algorithm
          class="type">class="kw">datetime startTime = TimeCurrent();
          class="type">class="kw">datetime endTime = startTime + class="num">3600; class=class="str">"cmt">// class="num">1 hour execution window
          
          if(executionManager.CreateTWAP(Symbol(), volume, startTime, endTime,
                                          executionIntervals, ORDER_TYPE_BUY))
          {
              executionManager.Initialize();
              inPosition = true;
              Print("Buy signal detected. Starting TWAP execution.");
          }
      }
  }
  else
  {
      class=class="str">"cmt">// Update the execution algorithm
      if(executionManager.Update())
      {
          class=class="str">"cmt">// Check if execution is complete
          if(!executionManager.GetAlgorithm().IsActive())
          {
              inPosition = false;
              Print("TWAP execution completed.");
          }
      }
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「RSI 极值触发下的 VWAP 拆单进场」

均值回归思路里,RSI 触碰到超卖或超买边界常被用作反向进场信号。下面这段 MT5 脚本把 RSI(14) 的 30/70 阈值和 VWAP 执行算法绑在一起:当 RSI 低于 30 尝试在 1 小时内按成交量加权均价拆成 5 笔买入,高于 70 则对称拆出卖出。 外汇与贵金属这类高波动品种,RSI 极值后价格可能继续惯性延伸,拆单能摊薄滑点但无法消除方向误判风险,实盘前务必在策略测试器里跑过历史样本。 代码里用 static 变量锁住 inPosition / isLong 状态,避免同方向重复触发;executionManager.CreateVWAP 的窗口写死为 TimeCurrent()+3600 秒,也就是固定 1 小时执行期,想拉长窗口直接改这个加法即可。

MQL5 / C++
class=class="str">"cmt">//| Mean-Reversion Strategy with VWAP Execution                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
  class=class="str">"cmt">// Strategy parameters
  class="type">int rsiPeriod = class="num">14;
  class="type">int rsiOversold = class="num">30;
  class="type">int rsiOverbought = class="num">70;
  class="type">class="kw">double volume = class="num">1.0;
  class="type">int executionIntervals = class="num">5;

  class=class="str">"cmt">// Calculate indicators
  class="type">class="kw">double rsi = iRSI(Symbol(), PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE, class="num">0);

  class=class="str">"cmt">// Check for entry conditions
  class="kw">static class="type">bool inPosition = false;
  class="kw">static class="type">bool isLong = false;
  class="kw">static CExecutionManager executionManager;

  if(!inPosition)
  {
    class=class="str">"cmt">// Buy signal: RSI oversold
    if(rsi < rsiOversold)
    {
      class=class="str">"cmt">// Create VWAP execution algorithm
      class="type">class="kw">datetime startTime = TimeCurrent();
      class="type">class="kw">datetime endTime = startTime + class="num">3600; class=class="str">"cmt">// class="num">1 hour execution window

      if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime,
                                     executionIntervals, ORDER_TYPE_BUY))
      {
        executionManager.Initialize();
        inPosition = true;
        isLong = true;
        Print("Buy signal detected. Starting VWAP execution.");
      }
    }
    class=class="str">"cmt">// Sell signal: RSI overbought
    else if(rsi > rsiOverbought)
    {
      class=class="str">"cmt">// Create VWAP execution algorithm
      class="type">class="kw">datetime startTime = TimeCurrent();
      class="type">class="kw">datetime endTime = startTime + class="num">3600; class=class="str">"cmt">// class="num">1 hour execution window

      if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime,
                                     executionIntervals, ORDER_TYPE_SELL))
      {
        executionManager.Initialize();
        inPosition = true;
        isLong = false;
        Print("Sell signal detected. Starting VWAP execution.");
      }
    }
  }
  else
  {
    class=class="str">"cmt">// Update the execution algorithm
    if(executionManager.Update())
    {
      class=class="str">"cmt">// Check if execution is complete

冰山单接突破通道的入场写法

VWAP 执行器跑完之后,若算法状态回到非活跃,会直接把 inPosition 置否并打印完成信息;这一步是防止上一轮挂单逻辑污染新策略的必要清场。 退出判定挂在 RSI 极端值上:多单且 rsi>rsiOverbought 调 Terminate() 平仓,空单且 rsi<rsiOversold 同理。外汇与贵金属波动下,这种硬阈值退出可能频繁触发,实盘前建议先在 MT5 策略测试器跑一遍周期样本。 下面的突破策略用 20 根 K 线的高低通道:upperChannel 取 iHighest 的 MODE_HIGH,lowerChannel 取 iLowest 的 MODE_LOW,当前价用 SYMBOL_BID 读取。 当 currentPrice>upperChannel 且未持仓,就建冰山买入单:总手数 1.0、可见 0.1,隐藏区间限制在可见量的 0.8~1.2 倍,偏移 1000 点、成交尝试 2 次、失效 3 根。可见量压到总仓 10%,能在薄流动品种里降低扫单痕迹。

MQL5 / C++
if(!executionManager.GetAlgorithm().IsActive())
  {
    inPosition = false;
    Print("VWAP execution completed.");
  }

class=class="str">"cmt">// Check for exit conditions
if(isLong && rsi > rsiOverbought)
{
   executionManager.Terminate();
   inPosition = false;
   Print("Exit signal detected. Terminating VWAP execution.");
}
else if(!isLong && rsi < rsiOversold)
{
   executionManager.Terminate();
   inPosition = false;
   Print("Exit signal detected. Terminating VWAP execution.");
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Breakout Strategy with Iceberg Orders                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
  class=class="str">"cmt">// Strategy parameters
  class="type">int channelPeriod = class="num">20;
  class="type">class="kw">double volume = class="num">1.0;
  class="type">class="kw">double visibleVolume = class="num">0.1;

  class=class="str">"cmt">// Calculate indicators
  class="type">class="kw">double upperChannel = iHigh(Symbol(), PERIOD_CURRENT, iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, channelPeriod, class="num">1));
  class="type">class="kw">double lowerChannel = iLow(Symbol(), PERIOD_CURRENT, iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, channelPeriod, class="num">1));

  class="type">class="kw">double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID);

  class=class="str">"cmt">// Check for entry conditions
  class="kw">static class="type">bool inPosition = false;
  class="kw">static CExecutionManager executionManager;

  if(!inPosition)
  {
    class=class="str">"cmt">// Buy signal: Price breaks above upper channel
    if(currentPrice > upperChannel)
    {
      class=class="str">"cmt">// Create Iceberg Order
      class="type">class="kw">double limitPrice = upperChannel; class=class="str">"cmt">// Place limit order at breakout level

      if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_BUY,
                            visibleVolume, visibleVolume * class="num">0.8, visibleVolume * class="num">1.2,
                            true, class="num">1000, true, class="num">2, class="num">3))
      {
        executionManager.Initialize();
        inPosition = true;

◍ 下破通道时的冰山卖单触发逻辑

当价格向下击穿通道下轨 lowerChannel,系统判定为卖出突破信号,随即在该价位挂出冰山限价卖单。CreateIcebergOrder 的调用里,可见成交量 visibleVolume 作为基准,隐藏区间被设为 0.8~1.2 倍,也就是说真实挂单厚度可能在账面显示量的八成到一倍二之间浮动,用来降低被对手盘扫单的概率。 参数中倒数第三、第四项的 1000 与 true,分别代表刷新间隔毫秒数与允许部分成交;最后的 2 与 3 是最小拆单笔数和最大重试次数。若创建成功,executionManager.Initialize() 启动算法,inPosition 置真并打印日志,实盘里你能在 MT5 专家日志看到「Sell breakout detected」这一行。 非信号分支里,已持仓时每 tick 调用 Update() 推进冰山算法;一旦 GetAlgorithm().IsActive() 返回 false,说明大单已拆完,inPosition 复位并打印「completed」。外汇与贵金属杠杆高,冰山单只缓解暴露度,不消除滑点与反向突破风险,参数请先在策略测试器跑通再上真仓。

MQL5 / C++
      Print("Buy breakout detected. Starting Iceberg Order execution.");
      }
      }
      class=class="str">"cmt">// Sell signal: Price breaks below lower channel
      else if(currentPrice < lowerChannel)
      {
      class=class="str">"cmt">// Create Iceberg Order
      class="type">class="kw">double limitPrice = lowerChannel; class=class="str">"cmt">// Place limit order at breakout level
      
      if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_SELL,
                                              visibleVolume, visibleVolume * class="num">0.8, visibleVolume * class="num">1.2,
                                              true, class="num">1000, true, class="num">2, class="num">3))
      {
        executionManager.Initialize();
        inPosition = true;
        Print("Sell breakout detected. Starting Iceberg Order execution.");
      }
      }
  }
  else
  {
    class=class="str">"cmt">// Update the execution algorithm
    if(executionManager.Update())
    {
      class=class="str">"cmt">// Check if execution is complete
      if(!executionManager.GetAlgorithm().IsActive())
      {
        inPosition = false;
        Print("Iceberg Order execution completed.");
      }
    }
  }
}

「把趋势信号和执行算法拆开跑」

这套 EA 的思路是把「看方向」和「怎么下单」彻底分开:外层只负责用双 SMA 交叉判趋势,内层交给执行管理器去选 TWAP、VWAP 或冰山。这样同一套界面能驱动多种拆单方式,不用为每个算法重写样板。 输入参数给了四个可调项:快线周期默认 20、慢线 50、单信号总交易量 0.1 手,以及一个自适应开关 UseAdaptiveExecution(默认 true)。全局只留一个 CExecutionManager 指针加两个指标句柄,保证跨 tick 不丢状态。 OnInit 里先 new 出执行管理器,传当前品种、最多 3 个并发算法和自适应标志;再用 iMA 建两条 SMA 句柄。任一句柄返回 INVALID_HANDLE 就直接 INIT_FAILED 中止,避免空句柄进主循环。 自适应关掉时,你可以手动起一个 TWAP 实例跑 1 小时、切 6 片;开着时就把信号和量丢给 ExecuteSignal(),由管理器按盘口流动性挑算法。外汇和贵金属波动大、滑点不可控,这种拆单只降低冲击成本,不保证盈利。 下面这段是加了止盈后的头文件和初始化骨架,注意 metadata 和 include 顺序——ExecutionManager 必须最后包含,否则其依赖的类还没声明。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IntegratedStrategy.mq5          |
class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Copyright class="num">2025, MetaQuotes Software Corp. |
class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;https://www.metaquotes.net&nbsp;&nbsp;&nbsp;&nbsp;|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Software Corp."
class="macro">#class="kw">property link&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"https:class=class="str">"cmt">//www.metaquotes.net"
class="macro">#class="kw">property version&nbsp;&nbsp; "class="num">1.00"
class="macro">#include "ExecutionAlgorithm.mqh"
class="macro">#include "TWAP.mqh"
class="macro">#include "VWAP.mqh"
class="macro">#include "IcebergOrder.mqh"
class="macro">#include "PerformanceAnalyzer.mqh"
class="macro">#include "ExecutionManager.mqh"
class=class="str">"cmt">// Input parameters
input class="type">int FastMA = class="num">20;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;        class=class="str">"cmt">// Fast moving average period
input class="type">int SlowMA = class="num">50;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;        class=class="str">"cmt">// Slow moving average period
input class="type">class="kw">double TradingVolume = class="num">0.1;       class=class="str">"cmt">// Trading volume
input class="type">bool UseAdaptiveExecution = true; class=class="str">"cmt">// Use adaptive execution based on market conditions
class=class="str">"cmt">// Global variables
CExecutionManager *g_executionManager = NULL;
class="type">int g_maHandle1 = INVALID_HANDLE;
class="type">int g_maHandle2 = INVALID_HANDLE;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
&nbsp;&nbsp; class=class="str">"cmt">// Initialize execution manager
&nbsp;&nbsp; g_executionManager = new CExecutionManager(Symbol(), class="num">3, UseAdaptiveExecution);
&nbsp;&nbsp;
&nbsp;&nbsp; class=class="str">"cmt">// Initialize indicators
&nbsp;&nbsp; g_maHandle1 = iMA(Symbol(), Period(), FastMA, class="num">0, MODE_SMA, PRICE_CLOSE);
&nbsp;&nbsp; g_maHandle2 = iMA(Symbol(), Period(), SlowMA, class="num">0, MODE_SMA, PRICE_CLOSE);
&nbsp;&nbsp;
&nbsp;&nbsp; if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE)
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Print("Failed to create indicator handles");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="kw">return INIT_FAILED;
&nbsp;&nbsp; }
&nbsp;&nbsp;
&nbsp;&nbsp; class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

EA退出与Tick里的双均线触发逻辑

EA卸载时先把执行管理器实例 delete 掉并置空,再调用 IndicatorRelease 释放两个均线句柄,避免 MT5 终端退出时残留对象句柄导致内存泄漏。 OnTick 里第一行就让 g_executionManager.UpdateAlgorithms() 刷新算法状态;随后用 iVolume(_Symbol, PERIOD_CURRENT, 0) > 1 做过滤,当前 K 线成交量大于 1 就直接 return,保证信号只在 Bar 开盘那一 tick 处理。 快慢均线各取最近 2 根缓冲值(fastMA[2]、slowMA[2]),CopyBuffer 任一返回 <=0 就 Print 报错并退出。金叉判定为 fastMA[0]>slowMA[0] 且 fastMA[1]<=slowMA[1],死叉反之——这是基于两根 BAR 的穿越,不是单根抖动。 若 UseAdaptiveExecution 为 true,买入信号交给执行管理器自动选算法;否则手动建 CTWAP,把当前时间到 currentTime+3600 秒(1小时)切成 6 段均量单。外汇与贵金属杠杆高,TWAP 拆单只降低冲击成本,不消除方向性亏损可能。

MQL5 / C++
class=class="str">"cmt">//| Expert deinitialization function                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
  class=class="str">"cmt">// Clean up
  if(g_executionManager != NULL)
  {
    class="kw">delete g_executionManager;
    g_executionManager = NULL;
  }

  class=class="str">"cmt">// Release indicator handles
  IndicatorRelease(g_maHandle1);
  IndicatorRelease(g_maHandle2);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
  class=class="str">"cmt">// Update execution algorithms
  if(g_executionManager != NULL)
    g_executionManager.UpdateAlgorithms();

  class=class="str">"cmt">// Only process at bar open
  if(iVolume(_Symbol, PERIOD_CURRENT, class="num">0) > class="num">1)
    class="kw">return;

  class=class="str">"cmt">// Get indicator values
  class="type">class="kw">double fastMA[class="num">2], slowMA[class="num">2];

  if(CopyBuffer(g_maHandle1, class="num">0, class="num">0, class="num">2, fastMA) <= class="num">0 ||
     CopyBuffer(g_maHandle2, class="num">0, class="num">0, class="num">2, slowMA) <= class="num">0)
  {
    Print("Failed to copy indicator buffers");
    class="kw">return;
  }

  class=class="str">"cmt">// Check for trend signals
  class="type">bool buySignal = (fastMA[class="num">0] > slowMA[class="num">0]) && (fastMA[class="num">1] <= slowMA[class="num">1]);
  class="type">bool sellSignal = (fastMA[class="num">0] < slowMA[class="num">0]) && (fastMA[class="num">1] >= slowMA[class="num">1]);

  class=class="str">"cmt">// Execute signals using the execution manager
  if(buySignal)
  {
    Print("Buy signal detected");

    if(UseAdaptiveExecution)
    {
      class=class="str">"cmt">// Let the execution manager select the best algorithm
      g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume);
    }
    else
    {
      class=class="str">"cmt">// Manually create a TWAP algorithm
      class="type">class="kw">datetime currentTime = TimeCurrent();
      CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime,
                                                  currentTime + class="num">3600, class="num">6,
                                                  ORDER_TYPE_BUY, true);
    }
  }
  else if(sellSignal)
  {

◍ 卖信号触发后的两种执行路径

当策略判定出现卖出信号时,先向日志打印 "Sell signal detected" 便于在 MT5 专家日志里肉眼追踪。接下来是否走自适应执行,由输入参数 UseAdaptiveExecution 控制:为真时直接交给执行管理器按市况挑算法,为假时则手动拼一个 TWAP 单。 手动模式下,先用 TimeCurrent() 取当前时间,再调 CreateTWAP 建一个 1 小时(3600 秒)内拆 6 笔的卖单 TWAP,被动挂单标志填 true。这样能在流动性差的外汇或贵金属时段摊薄冲击成本,但拆单本身不保证更好成交价,滑点风险仍随行情波动。 主文件 IntegratedStrategy.mq5 里把 ExecutionManager、TWAP、VWAP、IcebergOrder 等头文件一并 include,说明执行层是模块化挂载的。输入参数给出可直抄的默认值:快/慢均线 20/50,单笔交易量 0.1 手,权益止盈 10%、止损 5%——开 MT5 新建 EA 时可直接照这组数起手验逻辑。

MQL5 / C++
Print("Sell signal detected");

if(UseAdaptiveExecution)
{
   class=class="str">"cmt">// Let the execution manager select the best algorithm
   g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume);
}
else
{
   class=class="str">"cmt">// Manually create a TWAP algorithm
   class="type">class="kw">datetime currentTime = TimeCurrent();
   CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime,
                                                currentTime + class="num">3600, class="num">6,
                                                ORDER_TYPE_SELL, true);
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Software Corp."
class="macro">#class="kw">property link     "https:class=class="str">"cmt">//www.metaquotes.net"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include "ExecutionAlgorithm.mqh"
class="macro">#include "TWAP.mqh"
class="macro">#include "VWAP.mqh"
class="macro">#include "IcebergOrder.mqh"
class="macro">#include "PerformanceAnalyzer.mqh"
class="macro">#include "ExecutionManager.mqh"
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">// Input parameters
input class="type">int     FastMA               = class="num">20;     class=class="str">"cmt">// Fast moving average period
input class="type">int     SlowMA               = class="num">50;     class=class="str">"cmt">// Slow moving average period
input class="type">class="kw">double  TradingVolume        = class="num">0.1;    class=class="str">"cmt">// Trading volume
input class="type">bool    UseAdaptiveExecution = true;   class=class="str">"cmt">// Use adaptive execution based on market conditions
input class="type">class="kw">double  EquityTPPercent      = class="num">10.0;   class=class="str">"cmt">// Equity Take Profit in percent
input class="type">class="kw">double  EquitySLPercent      = class="num">5.0;    class=class="str">"cmt">// Equity Stop Loss in percent
class=class="str">"cmt">// Global variables
CExecutionManager *g_executionManager = NULL;
class="type">int               g_maHandle1      = INVALID_HANDLE;

「用权益曲线锁死账户回撤与止盈」

EA 启动瞬间先把账户净值记进 g_initialEquity,后续所有基于资金的止盈止损都以这个值为基线。OnInit 里还顺手建了快慢两条 SMA 句柄(周期参数 FastMA / SlowMA 来自外部输入),任意句柄拿不到就直接 INIT_FAILED,避免空指针后面炸锅。 OnDeinit 不只是释放指标句柄,还把 CExecutionManager 实例 delete 掉并置 NULL,MT5 里这种自管对象不清理,反复加载 EA 会悄悄漏内存。 CheckEquityTPandSL 是核心:实时净值对比基线算出的 tpEquity 与 slEquity。比如 EquityTPPercent=5、基线 10000,净值摸到 10500 就全平并重置基线为当前净值;EquitySLPercent=3 时跌到 9700 同样全平重置。这套逻辑让外汇或贵金属这种高波动品种能按资金曲线而非价格硬扛止损,但触发后基线漂移,连胜时会不断抬高止盈锚,回撤保护可能随之变薄。

MQL5 / C++
class="type">int                 g_maHandle2      = INVALID_HANDLE;
class="type">class="kw">double              g_initialEquity  = class="num">0.0;
CTrade              trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
   class=class="str">"cmt">// Record initial equity
   g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   class=class="str">"cmt">// Initialize execution manager
   g_executionManager = new CExecutionManager(Symbol(), class="num">3, UseAdaptiveExecution);
   class=class="str">"cmt">// Initialize indicators
   g_maHandle1 = iMA(Symbol(), Period(), FastMA, class="num">0, MODE_SMA, PRICE_CLOSE);
   g_maHandle2 = iMA(Symbol(), Period(), SlowMA, class="num">0, MODE_SMA, PRICE_CLOSE);
   if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) {
      Print("Failed to create indicator handles");
      class="kw">return INIT_FAILED;
   }
   class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason) {
   class=class="str">"cmt">// Clean up
   if(g_executionManager != NULL) {
      class="kw">delete g_executionManager;
      g_executionManager = NULL;
   }
   class=class="str">"cmt">// Release indicator handles
   IndicatorRelease(g_maHandle1);
   IndicatorRelease(g_maHandle2);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check equity-based TP and SL, then reset baseline                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckEquityTPandSL() {
   class="type">class="kw">double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   class="type">class="kw">double tpEquity      = g_initialEquity * (class="num">1.0 + EquityTPPercent / class="num">100.0);
   class="type">class="kw">double slEquity      = g_initialEquity * (class="num">1.0 - EquitySLPercent / class="num">100.0);
   if(currentEquity >= tpEquity) {
      Print("Equity Take Profit reached: ", currentEquity);
      CloseAllPositions();
      g_initialEquity = currentEquity;
      Print("Equity baseline reset to: ", g_initialEquity);
   } else if(currentEquity <= slEquity) {
      Print("Equity Stop Loss reached: ", currentEquity);
      CloseAllPositions();
      g_initialEquity = currentEquity;

主循环里怎么管仓位和信号

EA 在 OnTick 里先调用 CheckEquityTPandSL 核对权益阈值,再让执行管理器刷新算法;若 g_executionManager 非空才更新,避免空指针崩在实时行情上。 用 iVolume(_Symbol, PERIOD_CURRENT, 0) > 1 做过滤,只在当前 K 线刚开盘(成交量为 1)时跑策略,能避开同一根 bar 内重复触发。外汇与贵金属波动快,这种节流对滑点控制有实际意义,但高频时段仍可能漏掉中途反转。 双均线交叉判定写得很直白:快线现价大于慢线、且上一根快线小于等于慢线,视为买入触发;反之为卖出。CopyBuffer 取两个数组各 2 个元素,失败就 Print 并 return,不会带脏数据进场。 自适应开关打开时由管理器挑算法,关掉就手搓一个 TWAP:当前时间起、跨度 3600 秒、拆 6 笔吃单。下面这段是原文里清仓与 tick 主体的骨架,可直接贴进 MT5 看编译行为。

MQL5 / C++
class="type">void CloseAllPositions() {
  CPositionInfo  m_position;
  CTrade m_trade;
  for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) class=class="str">"cmt">// loop all Open Positions
    if(m_position.SelectByIndex(i)) { class=class="str">"cmt">// select a position
      m_trade.PositionClose(m_position.Ticket()); class=class="str">"cmt">// then class="kw">delete it --period
    }
}

class="type">void OnTick() {
  class=class="str">"cmt">// Check and reset equity thresholds
  CheckEquityTPandSL();
  class=class="str">"cmt">// Update execution algorithms
  if(g_executionManager != NULL)
    g_executionManager.UpdateAlgorithms();
  class=class="str">"cmt">// Only process at bar open
  if(iVolume(_Symbol, PERIOD_CURRENT, class="num">0) > class="num">1)
    class="kw">return;
  class=class="str">"cmt">// Get indicator values
  class="type">class="kw">double fastMA[class="num">2], slowMA[class="num">2];
  if(CopyBuffer(g_maHandle1, class="num">0, class="num">0, class="num">2, fastMA) <= class="num">0 ||
     CopyBuffer(g_maHandle2, class="num">0, class="num">0, class="num">2, slowMA) <= class="num">0) {
    Print("Failed to copy indicator buffers");
    class="kw">return;
  }
  class=class="str">"cmt">// Check for trend signals
  class="type">bool buySignal  = (fastMA[class="num">0] > slowMA[class="num">0]) && (fastMA[class="num">1] <= slowMA[class="num">1]);
  class="type">bool sellSignal = (fastMA[class="num">0] < slowMA[class="num">0]) && (fastMA[class="num">1] >= slowMA[class="num">1]);
  class=class="str">"cmt">// Execute signals using the execution manager
  if(buySignal) {
    Print("Buy signal detected");
    if(UseAdaptiveExecution) {
      class=class="str">"cmt">// Let the execution manager select the best algorithm
      g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume);
    } else {
      class="type">class="kw">datetime currentTime = TimeCurrent();
      CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime,
         currentTime + class="num">3600, class="num">6,

◍ 卖信号下的自适应与 TWAP 分仓切换

当 sellSignal 成立时,先向日志打印 "Sell signal detected",便于在 MT5 专家日志里确认信号触发节点。 若开启 UseAdaptiveExecution,直接交给执行管理器按内部算法处理 SIGNAL_TYPE_SELL,由它选最优成交路径;否则走手动 TWAP 逻辑。 手动分支里取 TimeCurrent() 为起点,CreateTWAP 以 TradingVolume 为总量、跨度 3600 秒(1 小时)、拆 6 笔子单、方向 ORDER_TYPE_SELL、立即发送为真。外汇与贵金属杠杆高,TWAP 只缓解冲击,不消除跳空滑点风险,参数请先在策略测试器验证。

MQL5 / C++
      ORDER_TYPE_BUY, true);
      }
   } else if(sellSignal) {
      Print("Sell signal detected");
      if(UseAdaptiveExecution) {
         class=class="str">"cmt">// Let the execution manager select the best algorithm
         g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume);
      } else {
         class="type">class="kw">datetime currentTime = TimeCurrent();
         CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime,
                                     currentTime + class="num">3600, class="num">6,
                                     ORDER_TYPE_SELL, true);
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「两个月回测里的曲线与数字」

绿色余额阶梯是 EA 平仓后落袋的权益,蓝色净值线则把持仓中的浮盈浮亏平滑进去。1 月到 3 月初整体向上,中间有几次 10%–16% 的回调,但下一轮盈利都能把坑填平,说明这系统在趋势段能吃肉,回撤也在可承受区。 底部存款负荷三角形随时间收窄,从约占净值 10% 起逐步缩小。因为用的是固定手数,权益涨了之后单笔风险占净值的比例反而降了,所以美元净值变大,回撤百分比却基本不动。 初始入金 1000 美元,两个月净利润 +703 美元,收益率约 70%;利润因子 2.34,预期收益每笔 2.34 美元,夏普 5.47。最大余额回撤 9.99%(156 点),最大净值回撤 15.89%(228 点),恢复因子 3.08,意味着净利润是最大回撤的三倍多。

  • 次交易(600 笔进出)、胜率 76%,平均盈利 5.39 美元、平均亏损 -7.31 美元。连胜最长 87 笔(+302 美元),连败最长 23 笔(-156 美元)。外汇与贵金属属高风险品种,平均亏幅大于平均盈利,若行情结构切换,这层不对称可能放大。

把算法插进自己的策略里

TWAP、VWAP 和冰山订单这套执行层,核心价值不在于单独跑,而在于能塞进你已有的交易逻辑。模块化框架里用一个 ExecutionManager 外界面就能在运行中换算法、叠算法,PerformanceAnalyzer 则逐点记滑点、亏损和市场冲击,精度到小数点后最后一位。 实测社区反馈里有个坑值得记:VWAP.mqh 第 271、272、449 行以及 PerformanceAnalyzer.mqh 第 222 行、ExecutionManager.mqh 第 418 行,原代码把 long 直接赋给 double 会出类型转换警告;有用户改成 m_volumeProfile[intervalIndex] += (double)rates[i].tick_volume 后警告消除。外汇和贵金属杠杆高、滑点凶,这类隐式转换在极端行情可能放大统计偏差,建议动手前先修。 文件清单里 IntegratedStrategy.mq5 是集成示例 EA,另一版 IntegratedStrategy_-_Take_Profit.mq5 额外把仓位挂钩账户余额百分比并挂 SL/TP。回测时注意:有用户在 H1 上跑原版从未触发 SL/TP、单子全亏,评论区推测是周期和货币对不对路;想复现效果,自己换 M5 或 M1 拿主流货币对重跑最直观。 让小布替你跑这套 把 ZIP 里八个文件拖进 MT5 的 MQL5/Include 与 Experts 目录,先编译 ExecutionAlgorithm.mqh 基类,再挂 IntegratedStrategy 到 EURUSD 的 M5 图表,观察 PerformanceAnalyzer 输出的滑点曲线是否比你手手动市价单低。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 所有执行算法的基类|
class=class="str">"cmt">//+------------------------------------------------------------------+
class CExecutionAlgorithm
{
class="kw">protected:
   class="type">class="kw">string            m_symbol;          class=class="str">"cmt">// 交易符号
   class="type">class="kw">double            m_totalVolume;     class=class="str">"cmt">// 执行总量
   class="type">class="kw">double            m_executedVolume;  class=class="str">"cmt">// 卷已执行
   class="type">class="kw">double            m_remainingVolume; class=class="str">"cmt">// 剩余执行量
   class="type">class="kw">datetime          m_startTime;       class=class="str">"cmt">// 开始执行时间
   class="type">class="kw">datetime          m_endTime;         class=class="str">"cmt">// 执行结束时间
   class="type">int               m_slippage;        class=class="str">"cmt">// 允许的滑移点数

◍ 执行算法基类的成员与构造逻辑

在 MT5 写一套算法拆单框架,第一步是把执行算法的公共状态收进一个基类。下面这段 CExecutionAlgorithm 的声明里,m_isActive 控制算法开关,m_avgExecutionPrice、m_totalOrders、m_filledOrders 三组字段负责沉淀统计:平均成交价、总发单数与实际成交数,复盘时能直接拉出来算命中率。 基类把生命周期拆成 Initialize / Execute / Update / Terminate 四个虚函数,其中后三个纯虚(=0),意味着具体策略(如 TWAP、VWAP)必须自己实现循环与退出条件。常用方法里 IsActive()、GetRemainingVolume() 都是内联返回成员变量,实盘里可以高频轮询而不必担心调用开销。 构造函数只做一件事:把外部传进来的 symbol、volume、startTime、endTime、slippage 落进成员,并把 m_executedVolume 与 m_remainingVolume 初始化为 0 和总volume,m_isActive 默认 false。注意 m_avgExecutionPrice 初始为 0.0,若首笔未成交就读取它,会得到失真均值——调用前应先看 m_filledOrders 是否大于 0。 外汇与贵金属杠杆高、滑点跳变频繁,这类基类仅规范了数据结构,真实填单模式要靠 GetFillingMode() 按品种返回,回测与实盘可能倾向出现不一致,务必在策略层做隔离。

MQL5 / C++
class="type">bool m_isActive; class=class="str">"cmt">// 算法当前是否处于活动状态

class=class="str">"cmt">// 统计数据
class="type">class="kw">double m_avgExecutionPrice; class=class="str">"cmt">// 平均执行价格
class="type">int m_totalOrders; class=class="str">"cmt">// 订单总数
class="type">int m_filledOrders; class=class="str">"cmt">// 已成交订单的数量

class="kw">public:
class=class="str">"cmt">// 构造函数
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=class="str">"cmt">// 销毁器
class="kw">virtual ~CExecutionAlgorithm();

class=class="str">"cmt">// 由派生类实现的虚拟方法
class="kw">virtual class="type">bool Initialize();
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="num">0;

class=class="str">"cmt">// 常用方法
class="type">bool IsActive() { class="kw">return m_isActive; }
class="type">class="kw">double GetExecutedVolume() { class="kw">return m_executedVolume; }
class="type">class="kw">double GetRemainingVolume() { class="kw">return m_remainingVolume; }
class="type">class="kw">double GetAverageExecutionPrice() { class="kw">return m_avgExecutionPrice; }

class=class="str">"cmt">// 辅助方法
class="type">bool PlaceOrder(ENUM_ORDER_TYPE orderType, class="type">class="kw">double volume, class="type">class="kw">double price = class="num">0.0);
class="type">bool ModifyOrder(class="type">ulong ticket, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp);
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">// 获取适当填充模式的辅助方法
ENUM_ORDER_TYPE_FILLING GetFillingMode();
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 构造函数|
class=class="str">"cmt">//+------------------------------------------------------------------+
CExecutionAlgorithm::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)
{
  m_symbol = symbol;
  m_totalVolume = volume;
  m_executedVolume = class="num">0.0;
  m_remainingVolume = volume;
  m_startTime = startTime;
  m_endTime = endTime;
  m_slippage = slippage;
  m_isActive = false;

  m_avgExecutionPrice = class="num">0.0;
  m_totalOrders = class="num">0;
  m_filledOrders = class="num">0;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 销毁器|
class=class="str">"cmt">//+------------------------------------------------------------------+
CExecutionAlgorithm::~CExecutionAlgorithm()
{

「执行算法的初始化与下单骨架」

算法类 CExecutionAlgorithm 的 Initialize() 先把交易品种和总成交量做底线校验:品种为空或总量 ≤ 0.0 直接返回 false,避免在 MT5 实盘里发出无意义的请求。随后用 SymbolSelect(m_symbol, true) 把品种拉进市场观察窗口,拿不到句柄就退出,这一步能规避「品种未订阅导致报价为 0」的常见坑。 初始化通过后会重置统计字段:已成交量为 0.0、剩余成交量等于总计划量、加权成交价归零、订单计数全清。这意味着每次重新 Initialize 都会把算法状态机拨回起点,适合在切换品种或重新拆分大单前调用。 GetFillingMode() 按 FOK → IOC → RETURN 的优先级向券商要成交模式。MT5 中 SYMBOL_FILLING_MODE 返回的是位掩码,用按位与判断支持情况;若你的账户只支持 IOC,算法会自然降级,不会硬塞 FOK 导致拒单。 PlaceOrder() 接收市价或挂单类型,volume 必须 > 0.0。市价单走 TRADE_ACTION_DEAL,买用 SYMBOL_ASK、卖用 SYMBOL_BID 取实时价;挂单走 TRADE_ACTION_PENDING,price 若 ≤ 0.0 则交由后续逻辑处理。magic 写死 123456 仅作订单识别标签,你可改成策略 ID 以便回测区分。 外汇与贵金属杠杆高、滑点跳空频繁,FOK 在流动性差时可能完全不成交,IOC 则可能部分成交,实盘前务必在策略测试器用历史数据跑一遍填充模式表现。

MQL5 / C++
class=class="str">"cmt">// 必要时清理资源
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// | 初始化算法|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExecutionAlgorithm::Initialize()
{
  class=class="str">"cmt">// 验证输入
  if(m_symbol == "" || m_totalVolume <= class="num">0.0)
  {
    Print("Invalid inputs for execution algorithm");
    class="kw">return false;
  }

  class=class="str">"cmt">// 检查符号是否存在
  if(!SymbolSelect(m_symbol, true))
  {
    Print("Symbol not found: ", m_symbol);
    class="kw">return false;
  }

  class=class="str">"cmt">// 重置统计数据
  m_executedVolume = class="num">0.0;
  m_remainingVolume = m_totalVolume;
  m_avgExecutionPrice = class="num">0.0;
  m_totalOrders = class="num">0;
  m_filledOrders = class="num">0;

  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 为符号获取合适的填充模式
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING CExecutionAlgorithm::GetFillingMode()
{
  class=class="str">"cmt">// 获取符号填充模式
  class="type">int filling_modes = (class="type">int)SymbolInfoInteger(m_symbol, SYMBOL_FILLING_MODE);

  class=class="str">"cmt">// 按偏好顺序检查可用的加注模式
  if((filling_modes & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
    class="kw">return ORDER_FILLING_FOK;
  else if((filling_modes & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
    class="kw">return ORDER_FILLING_IOC;
  else
    class="kw">return ORDER_FILLING_RETURN;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 下订单|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExecutionAlgorithm::PlaceOrder(ENUM_ORDER_TYPE orderType, class="type">class="kw">double volume, class="type">class="kw">double price = class="num">0.0)
{
  class=class="str">"cmt">// 验证输入
  if(volume <= class="num">0.0)
  {
    Print("Invalid order volume");
    class="kw">return false;
  }

  class=class="str">"cmt">// 准备请求
  class="type">MqlTradeRequest request;
  class="type">MqlTradeResult result;
  ZeroMemory(request);

  request.symbol = m_symbol;
  request.volume = volume;
  request.type = orderType;
  request.deviation = m_slippage;
  request.magic = class="num">123456; class=class="str">"cmt">// 用于识别的神奇数字

  class=class="str">"cmt">// 根据订单类型设置适当的操作和价格
  if(orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_SELL)
  {
    class=class="str">"cmt">// 市场订单
    request.action = TRADE_ACTION_DEAL;
    request.type_filling = GetFillingMode();

    if(orderType == ORDER_TYPE_BUY)
      request.price = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
    else
      request.price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
  }
  else
  {
    class=class="str">"cmt">// 挂单
    request.action = TRADE_ACTION_PENDING;
    if(price <= class="num">0.0)

挂单与市价单的发送及改撤逻辑

这段 MQL5 类方法把下单后的统计与改单、撤单流程拆得很清楚。市价单(ORDER_TYPE_BUY / ORDER_TYPE_SELL)在 OrderSend 返回 TRADE_RETCODE_DONE 后会立刻累加 m_filledOrders、已执行音量 m_executedVolume,并扣减 m_remainingVolume;挂单则只增 m_totalOrders,不碰执行统计。 发送订单不是调一次 OrderSend 就完事。必须先判返回值,再判 result.retcode 是否等于 TRADE_RETCODE_DONE,否则打印错误码和 broker 备注就退出,避免拿假成功当真成交。 ModifyOrder 用 TRADE_ACTION_MODIFY,把 ticket、新 price、sl、tp 填进 MqlTradeRequest;CancelOrder 用 TRADE_ACTION_REMOVE,只传 ticket。两者都复用同一套「发请求→查 retcode」的防御写法。 外汇与贵金属杠杆高,OrderSend 失败可能来自点差突变或流动性缺失,实盘前应在 MT5 策略测试器用历史数据跑一遍这几段,确认错误分支能被正确触发并打印。

MQL5 / C++
{
     Print("Price must be specified for pending orders");
     class="kw">return false;
   }
   request.price = price;
  }

  class=class="str">"cmt">// 发送订单
  if(!OrderSend(request, result))
  {
     Print("OrderSend error: ", GetLastError());
     class="kw">return false;
  }

  class=class="str">"cmt">// 检查结果
  if(result.retcode != TRADE_RETCODE_DONE)
  {
     Print("OrderSend failed with code: ", result.retcode, " - ", result.comment);
     class="kw">return false;
  }

  class=class="str">"cmt">// 更新统计数据
  m_totalOrders++;

  class=class="str">"cmt">// 对于市场订单,立即更新执行统计数据
  if(orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_SELL)
  {
     m_filledOrders++;
     UpdateAverageExecutionPrice(request.price, volume);
     m_executedVolume += volume;
     m_remainingVolume -= volume;
  }

  Print("Order placed successfully. Ticket: ", result.order, " Volume: ", volume, " Price: ", request.price);

  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 修改现有订单|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExecutionAlgorithm::ModifyOrder(class="type">ulong ticket, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp)
{
  class=class="str">"cmt">// 准备请求
  class="type">MqlTradeRequest request;
  class="type">MqlTradeResult result;
  ZeroMemory(request);

  request.action = TRADE_ACTION_MODIFY;
  request.order = ticket;
  request.price = price;
  request.sl = sl;
  request.tp = tp;

  class=class="str">"cmt">// 发送修改请求
  if(!OrderSend(request, result))
  {
     Print("OrderModify error: ", GetLastError());
     class="kw">return false;
  }

  class=class="str">"cmt">// 检查结果
  if(result.retcode != TRADE_RETCODE_DONE)
  {
     Print("OrderModify failed with code: ", result.retcode, " - ", result.comment);
     class="kw">return false;
  }

  Print("Order modified successfully. Ticket: ", ticket);

  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取消现有订单|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExecutionAlgorithm::CancelOrder(class="type">ulong ticket)
{
  class=class="str">"cmt">// 准备请求
  class="type">MqlTradeRequest request;
  class="type">MqlTradeResult result;
  ZeroMemory(request);

  request.action = TRADE_ACTION_REMOVE;
  request.order = ticket;

  class=class="str">"cmt">// 发送取消请求
  if(!OrderSend(request, result))
  {
     Print("OrderCancel error: ", GetLastError());
     class="kw">return false;
  }

  class=class="str">"cmt">// 检查结果
  if(result.retcode != TRADE_RETCODE_DONE)
  {

◍ 把这条线请下神坛

上面这段撤单失败回执和加权均价更新,是算法执行层的收尾两块。撤单若返回非零 retcode,直接打印错误码与经纪商注释并退出,不继续吃单;成功才回 true,Ticket 用于复盘逐笔成交。 UpdateAverageExecutionPrice 只在已成交量大于 0 时才做加权平均:旧均价乘旧量加新价乘新量,再除总量。首笔成交走 else 分支,均价就是那一口价格。 外汇与贵金属杠杆高,这类执行类代码只解决‘怎么成交’,不解决‘该不该成交’。把平均价当成本锚可以,但别把它当成胜率保障,行情跳空时加权均价可能滞后于真实浮亏。

MQL5 / C++
   Print("OrderCancel failed with code: ", result.retcode, " - ", result.comment);
   class="kw">return false;
   }

   Print("Order cancelled successfully. Ticket: ", ticket);

   class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 更新平均执行价格|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CExecutionAlgorithm::UpdateAverageExecutionPrice(class="type">class="kw">double price, class="type">class="kw">double volume)
{
   class=class="str">"cmt">// 计算新的平均执行价格
   if(m_executedVolume > class="num">0.0)
   {
      class=class="str">"cmt">// 新旧价格的加权平均数
      m_avgExecutionPrice = (m_avgExecutionPrice * m_executedVolume + price * volume) /
                            (m_executedVolume + volume);
   }
   else
   {
      class=class="str">"cmt">// 首次执行
      m_avgExecutionPrice = price;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

在收尾处统一返回执行结果枚举(如成功/部分/失败),并把未成交挂单量回写上下文,避免策略误判已平。
盯紧滑点均值、成交集中度、冲击成本三项的回测分布,用同品种同周期横向比,偏离基线超20%再调参。
小布会读你的品种流动性和挂单厚度,给出适用边界提示与执行质量回溯,你按它的概率倾向选就行。
TWAP 在亚盘薄市易暴露节奏,VWAP 遇突发数据会追错权重,冰山在急拉中剩单可能集体曝光,均属高风险。
回测同策略裸跑对比,优化幅度通常体现在冲击成本降几个基点,具体数字看品种波动,没有稳赚结论。