MQL5 酷客宝典: 读取持有锁仓仓位的属性·进阶篇
🔒

MQL5 酷客宝典: 读取持有锁仓仓位的属性·进阶篇

(2/3)· 锁仓账户下合计仓位不止买/卖两种状态,误判类型会让风控与对冲脚本直接算错净敞口

偏理论 第 2/3 篇
很多从 MT4 迁过来的脚本作者仍用 POSITION_TYPE_BUY/SELL 套锁仓账户,结果把锁定仓位当单向仓处理。净手数算错后,自动减仓和报警逻辑会整体偏移。先弄清五类合计仓位的定义,再写任何读取代码。

锁仓时间的取数与初始化校验

锁仓类在初始化阶段就拦掉了非外汇品种。若 symbol_calc_mode 不等于 SYMBOL_CALC_MODE_FOREX,直接打印提示并返回 false,意味着这套逻辑只适配外汇交易模式,碰贵金属或差价合约前得先改计算模式判断。 Time() 方法负责拿锁仓组合里最早的开仓时间。它先置 hedge_time 为 WRONG_VALUE,再遍历 m_tickets 里的每张持仓 ticket,用 SelectByTicket 选中后取 Time(),只要小于当前已记录值就更新,最终返回组合内最早的那一笔。 TimeUpdate() 的写法几乎一致,但初始值直接给 0 而非 WRONG_VALUE,返回的是最近更新时间。两个方法都依赖 m_tickets.Total() 大于 0 才有意义,空仓时前者回 WRONG_VALUE、后者回 0,调用方得自己区分这两种空值信号。 别把 WRONG_VALUE 和 0 混为一谈 Time() 空仓返回 WRONG_VALUE,TimeUpdate() 空仓返回 0,若上层只判断 >0 会漏掉前者的异常态,建议在 EA 里单独处理 WRONG_VALUE 分支。

MQL5 / C++
if(symbol_calc_mode!=SYMBOL_CALC_MODE_FOREX)
  {
    Print(__FUNCTION__+": 只用于外汇交易模式!");
    class="kw">return class="kw">false;
  }
m_magic=_magic;
class=class="str">"cmt">//---
  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取得锁仓开启的时间                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">datetime CHedgePositionInfo::Time(class="type">void)
  {
   class="type">class="kw">datetime hedge_time=WRONG_VALUE;
   class="type">int hedge_pos_num=m_tickets.Total();
class=class="str">"cmt">//--- 如果有仓位
   if(hedge_pos_num>class="num">0)
     {
      class=class="str">"cmt">//--- 找到第一个开启的仓位
      for(class="type">int pos_idx=class="num">0;pos_idx<hedge_pos_num;pos_idx++)
        {
         class="type">class="kw">ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
           if(m_pos_info.SelectByTicket(curr_pos_ticket))
             {
              class="type">class="kw">datetime curr_pos_time=m_pos_info.Time();
              if(curr_pos_time>class="num">0)
                {
                 if(hedge_time==class="num">0)
                    hedge_time=curr_pos_time;
                 else
                   {
                    if(curr_pos_time<hedge_time)
                       hedge_time=curr_pos_time;
                   }
                }
             }
        }
     }
class=class="str">"cmt">//---
   class="kw">return hedge_time;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取得锁仓更新的时间                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">datetime CHedgePositionInfo::TimeUpdate(class="type">void)
  {
   class="type">class="kw">datetime hedge_time_update=class="num">0;
   class="type">int hedge_pos_num=m_tickets.Total();
class=class="str">"cmt">//--- 如果有仓位
   if(hedge_pos_num>class="num">0)
     {
      class=class="str">"cmt">//--- 找到第一个开启的仓位
      for(class="type">int pos_idx=class="num">0;pos_idx<hedge_pos_num;pos_idx++)
        {
         class="type">class="kw">ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)

◍ 从持仓票里算出锁仓状态与最新更新时间

做锁仓面板时,最先要解决的是「这组仓位到底是什么形态」。下面这段逻辑先遍历持仓 ticket,抓出所有锁仓单里最新的 TimeUpdate,再按多空成交量判定类型。 更新时间那层判断很直接:用 SelectByTicket 选中持仓后读 TimeUpdate,只要大于 0 且比已记录的 hedge_time_update 更大,就覆盖。外汇与贵金属锁仓场景下,服务器返回的更新时间若异常为 0,这层保护能避免把无效值当最新。 判定类型靠 buy_volume 与 sell_volume 的比较:两者都大于 0 且买大于卖,归 HEDGE_NETTING_BUY;卖大于买则是 HEDGE_NETTING_SELL;相等就是标准 HEDGE_LOCKED。若只有一边有量,直接标 HEDGE_BUY 或 HEDGE_SELL。 把这段塞进 EA 的调试脚本,开 MT5 接一个同时持有 0.10 买和 0.06 卖的黄金锁仓账户,HedgeType() 应当返回 HEDGE_NETTING_BUY,可据此验证你的仓位聚合有没有漏算。

MQL5 / C++
if(m_pos_info.SelectByTicket(curr_pos_ticket))
  {
   class=class="str">"cmt">//--- 取得当前仓位的更新时间
   class="type">class="kw">datetime curr_pos_time_update=m_pos_info.TimeUpdate();
   if(curr_pos_time_update>class="num">0)
      if(curr_pos_time_update>hedge_time_update)
         hedge_time_update=curr_pos_time_update;
  }
 }
  }
class=class="str">"cmt">//---
  class="kw">return hedge_time_update;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取得锁仓类型                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_HEDGE_TYPE CHedgePositionInfo::HedgeType(class="type">void)
  {
  ENUM_HEDGE_TYPE curr_hedge_type=WRONG_VALUE;
  class="type">int hedge_pos_num=m_tickets.Total();
class=class="str">"cmt">//--- 如果有仓位
  if(hedge_pos_num>class="num">0)
    {
   class=class="str">"cmt">//--- 取得交易量
   class="type">class="kw">double total_vol,buy_volume,sell_volume;
   buy_volume=sell_volume=class="num">0.;
   total_vol=this.Volume(buy_volume,sell_volume);
   class=class="str">"cmt">//--- 定义锁仓类型
   if(buy_volume>class="num">0. && sell_volume>class="num">0.)
     {
     if(buy_volume>sell_volume)
        curr_hedge_type=HEDGE_NETTING_BUY;
     else if(buy_volume<sell_volume)
        curr_hedge_type=HEDGE_NETTING_SELL;
     else
        curr_hedge_type=HEDGE_LOCKED;
     }
   else if(buy_volume>class="num">0. && sell_volume==class="num">0.)
     curr_hedge_type=HEDGE_BUY;
   else if(buy_volume==class="num">0. && sell_volume>class="num">0.)
     curr_hedge_type=HEDGE_SELL;
    }
class=class="str">"cmt">//---
  class="kw">return curr_hedge_type;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取得锁仓交易量                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CHedgePositionInfo::Volume(class="type">class="kw">double &_buy_volume,class="type">class="kw">double &_sell_volume)
  {
  class="type">class="kw">double total_vol=class="num">0.;
  class="type">int hedge_pos_num=m_tickets.Total();
class=class="str">"cmt">//--- 如果有仓位
  if(hedge_pos_num>class="num">0)
    {
    _buy_volume=_sell_volume=class="num">0.;

「锁仓净量与均价的代码实现」

在 MT5 的 hedge 仓位管理类里,净头寸体积靠遍历 ticket 数组累加得出。下面这段循环把每个持仓的买卖体积分别塞进 _buy_volume 和 _sell_volume,最后用差值 total_vol=_buy_volume-_sell_volume 得到净方向暴露,外汇与贵金属锁仓组合的高杠杆风险也藏在净量符号里。 [CODE] //--- 取得买入/卖出交易量 for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++) { ulong curr_pos_ticket=m_tickets.At(pos_idx); if(curr_pos_ticket<LONG_MAX) if(m_pos_info.SelectByTicket(curr_pos_ticket)) { ENUM_POSITION_TYPE curr_pos_type=m_pos_info.PositionType(); double curr_pos_vol=m_pos_info.Volume(); if(curr_pos_vol>0.) { //--- 对于一个买入仓位 if(curr_pos_type==POSITION_TYPE_BUY) _buy_volume+=curr_pos_vol; //--- 对于卖出仓位 else if(curr_pos_type==POSITION_TYPE_SELL) _sell_volume+=curr_pos_vol; } } } total_vol=_buy_volume-_sell_volume; [/CODE] 逐行看:for 循环从 0 扫到 hedge_pos_num(持仓总数);At(pos_idx) 取第 pos_idx 个 ticket;SelectByTicket 选中该仓;PositionType 判多空,Volume 取手数,正数才计入对应方向。 均价计算部分,若基础与报价体积皆非零,用 fabs(hedge_quote_volume/hedge_base_volume) 取绝对值均价。例如 base=0.5 lot、quote=-75000 时,均价约 150000 报价单位/手,这数字可直接在策略测试器里核对。 手续费统计另起 Commission 方法,靠 HistorySelectByPosition(curr_pos_id) 拉出关联历史。curr_pos_id 来自 m_pos_info.Identifier(),必须大于 0 才有效,否则会漏算部分平仓费的返还。

MQL5 / C++
class=class="str">"cmt">//--- 取得买入/卖出交易量
for(class="type">int pos_idx=class="num">0;pos_idx<hedge_pos_num;pos_idx++)
  {
   class="type">class="kw">ulong curr_pos_ticket=m_tickets.At(pos_idx);
   if(curr_pos_ticket<LONG_MAX)
   if(m_pos_info.SelectByTicket(curr_pos_ticket))
     {
      class="type">ENUM_POSITION_TYPE curr_pos_type=m_pos_info.PositionType();
      class="type">class="kw">double curr_pos_vol=m_pos_info.Volume();
      if(curr_pos_vol>class="num">0.)
        {
         class=class="str">"cmt">//--- 对于一个买入仓位
         if(curr_pos_type==POSITION_TYPE_BUY)
            _buy_volume+=curr_pos_vol;
         class=class="str">"cmt">//--- 对于卖出仓位
         else if(curr_pos_type==POSITION_TYPE_SELL)
            _sell_volume+=curr_pos_vol;
        }
     }
  }
total_vol=_buy_volume-_sell_volume;

class=class="str">"cmt">//---
  class="kw">return total_vol;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 如果计算了锁仓交易量
if(hedge_base_volume!=class="num">0. && hedge_quote_volume!=class="num">0.)
  {
   _avg_pr=fabs(hedge_quote_volume/hedge_base_volume);
   _base_volume=hedge_base_volume;
   _quote_volume=hedge_quote_volume;
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 取得锁仓的手续费                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CHedgePositionInfo::Commission(class="kw">const class="type">bool _full=class="kw">false)
  {
   class="type">class="kw">double hedge_commission=class="num">0.;
   class="type">int hedge_pos_num=m_tickets.Total();
class=class="str">"cmt">//--- 如果有仓位
   if(hedge_pos_num>class="num">0)
      for(class="type">int pos_idx=class="num">0;pos_idx<hedge_pos_num;pos_idx++)
        {
         class="type">class="kw">ulong curr_pos_ticket=m_tickets.At(pos_idx);
         if(curr_pos_ticket<LONG_MAX)
         if(m_pos_info.SelectByTicket(curr_pos_ticket))
           {
            class="type">long curr_pos_id=m_pos_info.Identifier();
            if(curr_pos_id>class="num">0)
            class=class="str">"cmt">//--- 取得与所选仓位相关的交易历史 
            if(HistorySelectByPosition(curr_pos_id))

从成交历史里抠出锁仓佣金

统计锁仓总成本时,很多人只盯着当前持仓浮亏,却漏掉了已平仓对冲单的佣金。下面这段逻辑直接遍历 HistoryDealsTotal(),把每一笔入场成交的佣金按系数累加,返回的就是 hedge_commission。 代码里用 CDealInfo 按索引 SelectByIndex 逐笔取成交,只筛 ENUM_DEAL_ENTRY 为 DEAL_ENTRY_IN 的入场单;若 Commission() 不为 0,则 NormalizeDouble 到两位小数后计入。变量 _full 为 true 时 fac 取 2,意味着双向锁仓佣金翻倍统计,false 时只算单边。 外汇与贵金属杠杆高,佣金虽小但高频对冲下会明显侵蚀净值,建议把这段塞进自己的 EA 回测里,跑 100 笔 XAUUSD 锁仓记录核对一下总和是否和账户历史对得上。 别把经纪商返佣当净赚 部分平台宣传返佣,但 HistoryDealsTotal 里的 Commission 字段可能是负值,fac 乘完之后 hedge_commission 会变小甚至转负,这时候你的‘成本’其实是平台返给你的,别直接当亏损扣掉。

MQL5 / C++
{
   CDealInfo curr_deal;
   class="type">int deals_num=HistoryDealsTotal();
   for(class="type">int deal_idx=class="num">0;deal_idx<deals_num;deal_idx++)
     if(curr_deal.SelectByIndex(deal_idx))
       {
        ENUM_DEAL_ENTRY curr_deal_entry=curr_deal.Entry();
        if(curr_deal_entry==DEAL_ENTRY_IN)
          {
           class="type">class="kw">double curr_deal_commission=NormalizeDouble(curr_deal.Commission(),class="num">2);
           if(curr_deal_commission!=class="num">0.)
             {
              class="type">class="kw">double fac=class="num">1.;
              if(_full) fac=class="num">2.;
              hedge_commission+=(fac*curr_deal_commission);
             }
          }
       }
 }
class=class="str">"cmt">//---
   class="kw">return hedge_commission;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 选择锁仓仓位                                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHedgePositionInfo::Select(class="type">void)
  {
   class="type">class="kw">string hedge_symbol=m_symbol.Name();
class=class="str">"cmt">//--- 清除所有仓位 
   m_tickets.Shutdown();
class=class="str">"cmt">//--- 收集仓位 positions
   class="type">int pos_num=PositionsTotal();
   for(class="type">int pos_idx=class="num">0;pos_idx<pos_num;pos_idx++)
     if(m_pos_info.SelectByIndex(pos_idx))
       {
        class="type">class="kw">string curr_pos_symbol=m_pos_info.Symbol();
        class=class="str">"cmt">//--- 选择交易品种
        if(!StringCompare(hedge_symbol,curr_pos_symbol))

◍ 用幻数筛掉不属于自己的持仓

EA 在遍历持仓时,如果不加区分地把账户里所有单子都塞进操作列表,就可能去动别的策略挂的仓。上面这段逻辑就是靠 magic number 做隔离:当 m_magic 大于 0 时才比对,否则默认全盘接收。 具体看,先置 is_the_same_magic 为 true,若 m_magic>0 则取出当前持仓的 Magic() 值,不等就翻成 false。只有标记为同幻数,才把 Ticket() 写进 m_tickets 数组;写不进就打印失败并return false。 最后一行 return m_tickets.Total()>0 是硬门槛:一张票都没收集到,函数直接返回空。实战里把 m_magic 设成你 EA 专属的 4 位整数(如 8888),就能在 MT5 同一账户跑多套策略互不误伤,外汇与贵金属杠杆高,错平他人单子可能引发连锁风险。

MQL5 / C++
      {
         class=class="str">"cmt">//--- 如果根据幻数选择
         class="type">bool is_the_same_magic=true;
         if(m_magic>class="num">0)
          {
           class="type">long curr_pos_magic=m_pos_info.Magic();
           if(m_magic!=curr_pos_magic)
              is_the_same_magic=class="kw">false;
          }
         if(is_the_same_magic)
          {
           class="type">class="kw">ulong curr_pos_ticket=m_pos_info.Ticket();
           if(curr_pos_ticket>class="num">0)
            if(!m_tickets.Add(curr_pos_ticket))
             {
              PrintFormat(__FUNCTION__+": 增加 #%d 订单失败!",curr_pos_ticket);
              class="kw">return class="kw">false;
             }
          }
       }
     }
class=class="str">"cmt">//---
   class="kw">return m_tickets.Total()>class="num">0;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+

「用脚本把锁仓属性拽进日志」

教学用的 Test_hedge_properties.mq5 是一个轻量脚本,只在「专家」日志里把当前锁仓组的属性打印出来,方便你确认类接口读到的数据对不对。先拿 USDCHF 练手:账户里挂了 5 个同品种仓位,脚本跑完会列出每一单的 ticket、买卖方向、手数和开仓价。 上面那段日志是实跑样本,时间戳 2018.09.03 18:51:37,品种 Symbol 显示 USDCHF 而非脚本挂载的 AUDNZD——说明它是扫全账户锁仓组,不是只看图表品种。5 单分别是买 1.75@0.9716、买 2.55@0.97142、卖 3.00@0.97157、卖 4.50@0.97164、买 1.25@0.97205,净敞口倾向为空头,幻数 0,类型标记 HEDGE_NETTING_SELL。 再往上叠一层就是 HedgePropertiesEA.mq5:用标准库 CAppDialog 派生出 CHedgeDialog,在图表上画面板实时显示锁仓属性。难点在锁仓数量变动时下拉框会触发 OnChartEvent 无限循环,作者给处理函数加了限流——同一秒最多进一次。外汇和贵金属锁仓涉及杠杆与双向敞口,回测和实盘都属高风险,面板数值仅供参考。 脚本和 EA 的完整代码都在附加 zip 里,MT5 里挂上 AUDNZD 的 H1 图直接跑,就能对照日志验证你本地仓位的读取逻辑有没有偏差。

MQL5 / C++
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        ---== 锁仓属性==---
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        Symbol: USDCHF
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        总仓位 = class="num">5
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)                class="num">1) #class="num">293972991 买入 class="num">1.75 USDCHF class="num">0.97160000
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)                class="num">2) #class="num">293974150 买入 class="num">2.55 USDCHF class="num">0.97142000
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)                class="num">3) #class="num">293974889 卖出 class="num">3.00 USDCHF class="num">0.97157000
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)                class="num">4) #class="num">293975329 卖出 class="num">4.50 USDCHF class="num">0.97164000
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)                class="num">5) #class="num">293976289 买入 class="num">1.25 USDCHF class="num">0.97205000
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        幻数: class="num">0
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        时间: class="num">2018.08.class="num">29 class="num">17:class="num">15:class="num">44
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        时间 毫秒数: class="num">1535562944628
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        更新时间: class="num">2018.08.class="num">29 class="num">17:class="num">20:class="num">35
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        更新时间毫秒数: class="num">1535563235034
class="num">2018.09.class="num">03 class="num">18:class="num">51:class="num">37.078 Test_hedge_properties(AUDNZD,H1)        类型: HEDGE_NETTING_SELL
让小布替你跑这套类型判别
这些锁仓类型的判别与净手数计算,小布盯盘的 AIGC 诊断已内置,打开对应品种页即可直接看到合计仓位归类,不必自己拼枚举与加减。

常见问题

前者买卖交易量平衡、净敞口为零;后者买入总量大于卖出,残留净多仓,按交易量而非订单数判定。
可以,小布盯盘的品种页已内置 AIGC 诊断,会自动归类锁仓买入、净额买入、锁定等状态并给出净手数。
同方向多笔小单可能合计小于一笔反向大单,按订单数会误判优势方向,必须用交易量加减得出净仓。
偏理论层面分为锁仓买入、锁仓净额买入、锁仓卖出、锁仓净额卖出、锁仓锁定五类,净额系统则仅买或卖两种。