在 MetaTrader 5 里使用 HedgeTerminal (对冲终端) 面板进行双向交易和仓位对冲, 第二部分·进阶篇
🧩

在 MetaTrader 5 里使用 HedgeTerminal (对冲终端) 面板进行双向交易和仓位对冲, 第二部分·进阶篇

(2/3)· 从程序库安装到事务选择、错误追踪与同步异步模式,拆解面板背后的调用逻辑

含代码示例偏理论 第 2/3 篇

接上篇,我们继续深挖 HedgeTerminal 在 EA 侧的真实工作方式。很多交易者以为面板只是手动点仓的图形壳,实际上它的 API 层才是双向持仓能被程序化调度的关键。

◍ 用 HedgePosition 接口筛自己的锁仓单

MT5 的净持仓账户之外,锁仓(hedge)模式下的持仓不再走 PositionGetInteger,而是要用 HedgePositionGetInteger 这类专属接口。想只处理自己 EA 开的单,第一步就是拿 Magic 号做过滤,比如脚本里写死 ulong Magic=5760655 再去比对 HEDGE_POSITION_MAGIC,能直接跳过别人家的单子。 下面这段从历史事务里捞锁仓平仓记录的循环,是实盘里常要抄的逻辑:从 TransactionsTotal(MODE_HISTORY)-1 倒序扫,先用 TransactionSelect 按位置选事务,再排掉非 TRANS_HEDGE_POSITION 类型,最后用 Magic 和订单 id(例里卡的是 5917888)双重确认。倒序遍历是因为最近平仓通常在末尾,少跑几圈。 外汇和贵金属锁仓策略本身杠杆高、滑点乱,这类遍历只解决‘找到单’的问题,不暗示任何收益。开 MT5 把 Magic 改成你实盘 EA 的值,接在 OnTick 里跑一遍,看 printf 出来的条数是否和账户历史对得上。

MQL5 / C++
for(class="type">int i=TransactionsTotal(MODE_HISTORY)-class="num">1; i>=class="num">0; i--)
  {
   if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))class="kw">continue;      class=class="str">"cmt">// 选择事务 class="macro">#i;
   if(TransactionType()!=TRANS_HEDGE_POSITION)class="kw">continue;               class=class="str">"cmt">// 如果事务不是仓位 - 继续;
   if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)class="kw">continue;class=class="str">"cmt">// 如果仓位不是主要的 - 继续;
   class="type">ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);  class=class="str">"cmt">// 获取平仓订单 id;
   if(id!=class="num">5917888)class="kw">continue;                                           class=class="str">"cmt">// 如果仓位 id != class="num">5917888 - 继续;
  }

从持仓逆推某笔成交的执行价

在 MT5 的持仓历史排查里,常需要由仓位反查某一笔具体成交。下面这段逻辑先按 id 选持仓,再选其关联的平仓订单,最后在订单的成交序列中定位 deal_id=1197610 并打出执行价。 代码里用 HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL) 拿到该订单的成交总数,随后从尾到头遍历。实测日志显示:在 VTBR-12.14 的 D1 图表上,成交 #1197610 的执行价为 4735,对应平仓订单 #6389111、仓位 #5917888。 另一个坑是事务遍历忘了 TransactionSelect。若直接读 TransactionType() 而未先按位置选中历史事务,会返回 TRANS_NOT_DEFINED,报错原因 HEDGE_ERR_TRANS_NOTSELECTED。外汇与贵金属品种波动大、杠杆高,这类查询只用于复盘与风控核对,实盘信号须自行验证。

MQL5 / C++
printf("class="num">1: -> 选择仓位 #"+(class="type">class="kw">string)id);                                 class=class="str">"cmt">// 打印仓位 id;
if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))class="kw">continue;                   class=class="str">"cmt">// 选择平仓订单或继续;    
class="type">ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);                  class=class="str">"cmt">// 获取平仓订单 id;
printf("class="num">2: ----> 选择订单 #" + (class="type">class="kw">string)order_id);                       class=class="str">"cmt">// 打印订单 id;
class="type">int deals_total = (class="type">int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);   class=class="str">"cmt">// 获取已选订单的成交总数;
for(class="type">int deal_index = deals_total-class="num">1; deal_index >= class="num">0; deal_index--)      class=class="str">"cmt">// 搜索成交 #class="num">1197610...
  {
   if(!HedgeDealSelect(deal_index))class="kw">continue;                            class=class="str">"cmt">// 以索引选择成交或继续;
   class="type">ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                  class=class="str">"cmt">// 获取当前成交 id;
   if(deal_id != class="num">1197610)class="kw">continue;                                      class=class="str">"cmt">// 选择成交 #class="num">1197610;
   class="type">class="kw">double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);        class=class="str">"cmt">// 获取执行价格;
   printf("class="num">3: --------> 选择成交 #"+(class="type">class="kw">string)deal_id+                     class=class="str">"cmt">// 打印执行价格;
          ". Executed price = "+DoubleToString(price,class="num">0));
  }
}
for(class="type">int i=TransactionsTotal(MODE_HISTORY)-class="num">1; i>=class="num">0; i--)
  {
   class=class="str">"cmt">//if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))class="kw">continue;       // 忘记选择;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
     {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("错误, 事务类型未定义。原因: " + EnumToString(error));
     }
  }
class="macro">#include <Prototypes.mqh>
class="type">ulong Magic=class="num">5760655; class=class="str">"cmt">// MagicEx class="num">1.3.

「在 OnTick 里改写对冲仓位注释与抓取任务回执」

MT5 对冲账户下,EA 的 OnTick 不只是报价响应,也可以回扫历史事务去定位自己下的对冲仓位。上面这段逻辑从 TransactionsTotal(MODE_HISTORY)-1 倒序遍历,用 TransactionSelect 按位置抽取,再过滤掉非 TRANS_HEDGE_POSITION、Magic 不符、品种不对的条目,只对本 EA 的黄金或欧美对冲仓动手。 定位到目标后,代码拼了一个 HedgeTradeRequest,把 action 设成 REQUEST_MODIFY_COMMENT,exit_comment 写成「我的新注释」,交给 SendTradeRequest。若返回失败,就循环 TotalActionsTask() 把每个子动作的类型和返回码打出来——实跑日志里见过动作 #0 返回 10009(TRADE_RETCODE_PLACED),动作 #1 返回 10015(TRADE_RETCODE_INVALID_PRICE),说明挂单删除成了、重挂却因价格越界被拒。 任务状态别靠猜。HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS) 吐出 TASK_STATUS_COMPLETE / EXECUTING / FAILED / WAITING 四种枚举:执行中可 Sleep(200) 轮询,失败态下再 TotalActionsTask 逐条 EnumToString 打印,能直接看出哪一步断在无效价格或超时。 对冲属性整数枚举目前只暴露了 HEDGE_PROP_TIMEOUT 一个成员,意味着超时阈值是对冲任务层面少数可程序化读取的整型参数。外汇与贵金属杠杆高,这类改注释和查回执的操作只建议在模拟盘先验证,实盘误改 Magic 过滤条件可能漏掉真实仓位。

MQL5 / C++
class=class="str">"cmt">//| EA 即时报价函数                                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void OnTick()
  {
class=class="str">"cmt">//检测活跃仓位
   for(class="type">int i=TransactionsTotal(MODE_HISTORY)-class="num">1; i>=class="num">0; i--)
     {
       if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))class="kw">continue;
       ENUM_TRANS_TYPE type=TransactionType();
       if(type==TRANS_NOT_DEFINED)
         {
          ENUM_HEDGE_ERR error=GetHedgeError();
          printf("错误, 事务未定义。原因: "+EnumToString(error));
         }
       if(TransactionType()!=TRANS_HEDGE_POSITION)class="kw">continue;
       if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)class="kw">continue;
       if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())class="kw">continue;
       HedgeTradeRequest request;
       request.action=REQUEST_MODIFY_COMMENT;
       request.exit_comment="我的新注释";
       if(!SendTradeRequest(request)) class=class="str">"cmt">// 出错?
         {
          for(class="type">uint action=class="num">0; action < TotalActionsTask(); action++)
            {
             ENUM_TARGET_TYPE typeAction;
             class="type">int retcode=class="num">0;
             GetActionResult(action, typeAction, retcode);
             printf("动作#" + (class="type">class="kw">string)action + ": " + EnumToString(type) +(class="type">class="kw">string)retcode);
            }
         }
     }
  }
动作 #class="num">0 TARGET_DELETE_PENDING_ORDER class="num">10009 (TRADE_RETCODE_PLACED)
动作 #class="num">1 TARGET_SET_PENDING_ORDER class="num">10015 (TRADE_RETCODE_INVALID_PRICE)
ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
class="kw">switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("任务完成!");
      class="kw">break;
   case TASK_STATUS_EXECUTING:
      printf("任务正在执行。等待...");
      Sleep(class="num">200);
      class="kw">break;
   case TASK_STATUS_FAILED:
      printf("记录执行任务。打印记录...");
      for(class="type">int i=class="num">0;i<TotalActionsTask();i++)
        {
         ENUM_TARGET_TYPE type;
         class="type">uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      class="kw">break;
   case TASK_STATUS_WAITING:
      printf("任务很快开始。");
      class="kw">break;
  }
enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

◍ 遍历对冲事务时先卡死超时与错误分支

在调用 HedgeTerminalAPI 做持仓扫描前,先用 HedgePropertySetInteger 把 HEDGE_PROP_TIMEOUT 设成 30,意味着单次 API 调用等待远端 hedge 终端响应的时限是 30 毫秒,超时后函数会返回 false 而不是干等。这个参数不设置时,默认行为依赖库内部实现,实盘网络抖动下可能卡住 OnStart 主线程。 下面这段脚本骨架展示了怎么在 TransactionsTotal() 的倒序循环里逐个 TransactionSelect(i, SELECT_BY_POS, MODE_TRADES)。一旦选择失败,立刻用 GetHedgeError() 拿错误码、printf 出来、ResetHedgeError() 清掉,再 continue 跳到下一条,避免单条脏数据中断整轮扫描。 只筛 TRANS_HEDGE_POSITION 类型,再用 HedgePositionGetInteger(HEDGE_POSITION_DIRECTION) 取方向、HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED) 取实际成交价。外汇与贵金属对冲操作本身杠杆高、滑点风险大,这类扫描脚本只在策略测试器或低仓位验证环境跑,别直接挂实盘大资金。

MQL5 / C++
class="type">bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER class="kw">property, class="type">int value)
class="type">bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, class="num">30);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 sample_using_htapi.mq5 |
class=class="str">"cmt">//|      Copyright class="num">2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
class=class="str">"cmt">//|                   [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2014, Vasiliy Sokolov."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">// 包含 HedgeTerminalAPI 程序库的函数原型。
class="macro">#include <Prototypes.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 即时报价函数                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
  class=class="str">"cmt">// 在事务列表里搜索所有事务...
  for(class="type">int i=TransactionsTotal(); i>=class="num">0; i--)
    {
    if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                    class=class="str">"cmt">// 从活跃事务里选择
      {
      ENUM_HEDGE_ERR error=GetHedgeError();                                class=class="str">"cmt">// 若选择失败, 获取原因
      printf("选择事务错误 # "+(class="type">class="kw">string)i+". 原因: "+                        class=class="str">"cmt">// 打印原因
             EnumToString(error));
      ResetHedgeError();                                                    class=class="str">"cmt">// 清除错误
      class="kw">continue;                                                            class=class="str">"cmt">// 继续下一个事务
      }
    class=class="str">"cmt">// 仅处理对冲仓位
    if(TransactionType()==TRANS_HEDGE_POSITION)
      {
      class=class="str">"cmt">// --- 仓位说明 --- //
      ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                class=class="str">"cmt">// 获取方向说明
            HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
      class="type">class="kw">double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED); class=class="str">"cmt">// 获取持仓量

把对冲仓位的成交明细拆出来

想看清一个对冲仓位到底吃了多少滑点、交了多少佣金,得从订单和成交两层去捞数据,而不是只看持仓摘要。下面这段逻辑直接在持仓循环里取初始订单,再按成交索引把每笔 commission 累加。 string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL); // 获取持仓品名 if(!HedgeOrderSelect(ORDER_SELECTD_INIT)) continue; // 在持仓里选择初始订单 double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE); // 获取滑点 uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL); // 获取成交总数 随后用 deals_total 控制 for 循环,HedgeDealSelect 按索引定位每笔成交,deal_id 取最后一次成交标识,commissions 做浮点累加。 double commissions=0.0; ulong deal_id=0; for(uint d_index=0; d_index<deals_total; d_index++) { if(!HedgeDealSelect(d_index)) continue; // 按照其索引选择成交 deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID); // 获取成交标识 commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION); // 计算佣金 } 最后用 SymbolInfoInteger 拿品种精度 digits,printf 把方向、入场价、滑点、末笔成交号和佣金总和打印成一行。外汇与贵金属杠杆高,滑点和佣金会直接啃掉波段利润,跑这段代码前先在策略测试器用历史数据验证输出是否符合预期。

MQL5 / C++
class="type">class="kw">string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);              class=class="str">"cmt">// 获取持仓品名
class=class="str">"cmt">// --- 订单说明 --- //
if(!HedgeOrderSelect(ORDER_SELECTED_INIT)) class="kw">continue;                            class=class="str">"cmt">// 在持仓里选择初始订单
class="type">class="kw">double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);                    class=class="str">"cmt">// 获取滑点
class="type">uint deals_total = (class="type">uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);         class=class="str">"cmt">// 获取成交总数
class=class="str">"cmt">// --- 成交说明 --- //
class="type">class="kw">double commissions=class="num">0.0;
class="type">ulong deal_id=class="num">0;
class=class="str">"cmt">//在成交列表里搜索所有成交...
for(class="type">uint d_index=class="num">0; d_index<deals_total; d_index++)
  {
   if(!HedgeDealSelect(d_index)) class="kw">continue;                                      class=class="str">"cmt">// 按照其索引选择成交
   deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                                class=class="str">"cmt">// 获取成交标识
   commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);                    class=class="str">"cmt">// 计算佣金
  }
class="type">int digits = (class="type">int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
printf("仓位 #" + (class="type">class="kw">string)i + "; 方向 " + EnumToString(direction) +             class=class="str">"cmt">// 打印结果行
"; 入场价格 " + DoubleToString(price_entry, digits) +
"; 初始滑点 " + DoubleToString(slippage, class="num">2) + "; 最后成交标识 " +
(class="type">class="kw">string)deal_id + "; 佣金总和 " + DoubleToString(commissions, class="num">2));

「把比尔·威廉姆斯分型柱线写成可编译的枚举」

策略落地到 MT5 时,第一步是把嘴上的“牛/熊分型柱”翻译成编译器认得的类型。下面这段把柱线分成普通、熊性、牛性三类,熊性要求收盘落在高点三分之一内且自身最低价是 N 周期最低,牛性反之,N 默认取 2。 同时把极值和仓位入口也枚举化:极值分最高/最低,仓位分 BUY1(多单带止损)、BUY2(多单带止盈)、SELL1(空单带止盈)。这样后续下单函数直接用 switch 匹配,不至于写出一堆魔法数字。 输入参数里 N=2 控制分型回顾周期,OldPending=3 管过时挂单清理,Magic=2314 用作 EA 标识。外汇与贵金属杠杆高,这类自动化逻辑先在策略测试器跑历史再上模拟盘。 代码里 hFractals 用 INVALID_HANDLE 初始化,避免指标句柄未创建就调用返回野指针;lastTime 记上次新柱时间,是 DetectNewBar 去重的关键。

MQL5 / C++
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Prototypes.mqh>           class=class="str">"cmt">// 包含 HedgeTerminalAPI 程序库的函数原型
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 输入参数。                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">uint N=class="num">2;                     class=class="str">"cmt">// 极大/极小周期
input class="type">uint OldPending=class="num">3;            class=class="str">"cmt">// 过时挂单
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 的私有变量。                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">ulong Magic = class="num">2314;                 class=class="str">"cmt">// EA 魔幻数字
class="type">class="kw">datetime lastTime = class="num">0;              class=class="str">"cmt">// 函数 DetectNewBar 的最后记忆时间
class="type">int hFractals = INVALID_HANDLE;     class=class="str">"cmt">// 指标 &class="macro">#x27;分形&class="macro">#x27; 的句柄参阅: &class="macro">#x27;[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 比尔·威廉姆斯策略的柱线类型                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               class=class="str">"cmt">// 一般柱线。
   BAR_TYPE_BEARISH,                class=class="str">"cmt">// 该柱线收盘价位于高点三分之一处且它的最小值是 N 周期的最低
   BAR_TYPE_BULLISH,                class=class="str">"cmt">// 该柱线收盘价位于低点三分之一处且它的最大值是 N 周期的最高
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 极值类型。                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           class=class="str">"cmt">// 最高价极值
   TYPE_EXTREMUM_LOWEST             class=class="str">"cmt">// 最低价极值
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 仓位类型。                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      class=class="str">"cmt">// 多头仓位带止损
   ENTRY_BUY2,                      class=class="str">"cmt">// 多头仓位带止盈
   ENTRY_SELL1,                     class=class="str">"cmt">// 空头仓位带止盈
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到双向仓位的净敞口与对冲比,你专注决策就好。

常见问题

需在 MetaTrader 5 的数据文件夹里放置程序库并允许 DLL 导入,首次调用会从 EA 侧完成初始化,失败通常会返回特定错误码。
前者按事务索引选单,后者直接锁定某笔对冲订单,后续属性读取函数作用域取决于最近一次选择的对象类型。
可以,小布的品种页会按经纪商虚拟重复品种聚合显示,不区分手动还是 API 触发,外汇贵金属杠杆高,对冲也仅是概率上的风险缓冲。
配合 TotalActionsTask 与 GetActionResult 逐任务拉取结果,能看出是选单失败、属性越界还是发送请求被拒。
同步适合低频确权的脚本,异步更适合混沌类 EA 连续挂单,但需自行轮询任务状态避免漏单。