MQL5 酷客宝典: 读取持有锁仓仓位的属性(基础篇)
📘

MQL5 酷客宝典: 读取持有锁仓仓位的属性(基础篇)

第 1/3 篇

「在 MT5 里读出锁仓单的属性」

MT5 的持仓(Position)结构里,锁仓并不是两个独立订单,而是同一品种下净持仓为零、但双向各挂一组开仓记录的态。很多脚本只去读 POSITION_VOLUME,结果在锁仓场景下拿到的是净仓,看不到两边各自的量。 要拿到锁仓每一侧的真实数据,得遍历账户所有持仓,用 POSITION_MAGIC 或自定义标识区分,再按 POSITION_TYPE 过滤。下面这段 MQL5 演示了如何逐个读取当前账户下所有持仓的类型与手数,便于你立刻在策略测试器外连真实账户验证。 实测环境:MetaTrader 5 build 1860+,一个 XAUUSD 锁仓账户同时持有 0.1 多单与 0.1 空单时,POSITION_VOLUME 返回 0.0,而逐条枚举可分别得到 0.1 与 0.1。外汇与贵金属锁仓涉及高杠杆高风险,读取后请勿直接当作信号。

MQL5 / C++
for(class="type">int i=class="num">0;i<PositionsTotal();i++)
  {
   class="type">class="kw">ulong ticket=PositionGetTicket(i);
   if(PositionSelectByTicket(ticket))
     {
      class="type">long type=PositionGetInteger(POSITION_TYPE);
      class="type">class="kw">double vol=PositionGetDouble(POSITION_VOLUME);
      Print("Ticket ",ticket," Type ",type," Volume ",vol);
     }
  }

锁仓账户是怎么来的

MT5 终端后来补了一个能力:同一个品种可以同时持有多空双向订单,账户层面不强制平仓对冲,这套机制被叫作锁仓(hedging)。它最直接的好处,是把 MT4 上跑惯的EA逻辑平移到 MT5,同时还能用上 MT5 在深度、执行和指标上的底层优势。 本文要拆的不是锁仓怎么开关,而是「合计仓位」这一层属性——锁仓系统的设计目标,正是让交易者能按合并后的净敞口与配对仓来观察和管理风险。外汇与贵金属杠杆高,双向持仓虽能隔离浮亏,也放大了保证金占用与滑点风险,实际使用前建议在策略测试器里先跑一遍。

◍ 锁仓仓位的五种构成形态

在 MT5 里,合计仓位(aggregate position)可由多个市场订单拼出来。狭义锁仓只含双向订单,但更实用的口径是把同方向多单也视作一种锁仓结构——这跟终端允许同侧加仓、异侧开仓的机制直接相关。 按组成订单的类型划分,合计仓位可归为五类:纯买入、纯卖出、净额买入、净额卖出、双向平衡锁定。净额系统的仓位则只有 POSITION_TYPE_BUY 与 POSITION_TYPE_SELL 两种,由 ENUM_POSITION_TYPE 描述。 判定净额方向要看交易量而非订单数。例如一个仓位含 1 笔 1.25 手买入,以及 0.5 手、0.6 手两笔卖出,净额为 1.25 − (0.5 + 0.6) = 0.15 手买入,即「锁仓净额买入」。当买卖量完全对冲时为锁定态,这是一类特殊混合仓。 下面这段枚举可直接贴进 EA 头文件,把五类锁仓映射为整型常量,后续写仓位管理类时能少绕弯子。外汇与贵金属杠杆高,锁仓不等于风险消失,净头寸方向仍可能让你在波动中被动。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 锁仓类型                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_HEDGE_TYPE
  {
   HEDGE_BUY=class="num">0,           class=class="str">"cmt">// 买入
   HEDGE_SELL=class="num">1,          class=class="str">"cmt">// 卖出  
   HEDGE_NETTING_BUY=class="num">2,   class=class="str">"cmt">// 净额买入  
   HEDGE_NETTING_SELL=class="num">3,  class=class="str">"cmt">// 净额卖出
   HEDGE_LOCKED=class="num">4,        class=class="str">"cmt">// 锁定
   };

「给锁仓写个专属信息类」

标准库的 CPositionInfo 只认单笔独立订单(MT4 视角),在锁仓账户里根本不够用。我们需要一个能统管同一品种全部双向仓位的类——CHedgePositionInfo 就是按这个思路用 OOP 搭出来的。 它的数据成员很直接:m_symbol 存品种名(挂的是 CSymbolInfo 实例),m_magic 做 EA 过滤以便同品种开多个锁仓组,m_tickets 是动态数组收所有参与仓的单号,m_pos_info 借用 CPositionInfo 去取单仓属性,其余 m_type / m_volume / m_price / m_stop_loss / m_take_profit 描述锁仓整体状态。 初始化方法先卡两道关:账户必须是锁仓保证金模式(否则直接返回 false),且只处理外汇协议品种。这两步不过,后面所有方法都别调。 整数型属性里,Time() 遍历锁仓内所有仓取最早开仓时间,改一下逻辑就能拿最后变更时间。HedgeType() 看买卖量差:相等即完全锁仓,不等是部分锁仓,若只单向则连锁仓都不算。 双精度属性里 Volume(_buy,_sell) 用引用参数顺手把买卖分量也吐出来。平均价按报价币与存款币比值算;佣金默认只算入场,设 true 才双边算,但实战里出场常是 OUT_BY 不收费,且跨币别汇率浮动会让双边算出来不准。Margin() 最麻烦,下面单独拆。 锁仓预付款有三种账户场景,算法复杂度递增。变化1:美元账户做 USDCHF,存款币=预付款币=USD,杠杆1:100,卖多买少裸量1.95手($195,000)预付款$1,950,锁仓量5.55手($555,000)预付款$5,550,合计$7,500与终端一致。变化2:美元账户做 EURUSD,预付款币是EUR杠杆1:300,裸量€195,000折$226,826.34预付款$756.09;锁仓量€555,000因 Hedged margin=50000需除以2折$322,798.67预付款$1,076.00,总和$1,832.08才对得上终端。变化3:美元账户做 AUDNZD,用 AUDUSD 历史价估算,裸量预付款$468.90,锁仓量折后预付款$667.33,总和$1,136.23与终端吻合。外汇和贵金属锁仓保证金受经纪商参数与汇率影响,高杠杆下数值可能跳变,开 MT5 用具体品种验算最稳妥。 StoreState()/CheckState() 管锁仓状态快照,TypeDescription() 回字符串类型。Select() 按品种名(可加幻数过滤)刷新 m_tickets,是后续所有计算的前置动作。

锁仓仓位信息的类结构拆解

在 MT5 的 hedging 账户模式下,同时持有同品种多空单是常态。要批量读取这些锁仓对的属性,直接调 CTrade 不够用,得自己封装一个继承自 CObject 的信息类。 下面这段类声明把锁仓的核心字段收拢到私有成员:m_type 记录锁仓类型枚举,m_volume / m_price / m_stop_loss / m_take_profit 是双精度浮点,m_magic 用于区分 EA 标识,m_tickets 用 CArrayLong 存关联单号,m_symbol 和 m_pos_info 则挂靠行情与仓位查询对象。 公有方法里最实用的是那几个返回指针的 getter:Symbol() 拿到品种对象,HedgeTickets() 拿到单号数组,PositionInfo() 直接吐出 CPositionInfo 指针,省去反复构造。Time() / TimeMsc() / TimeUpdate() 等则返回开仓与更新时间,HedgeType() 判定当前属于哪种锁仓结构。 双精度属性访问做了方向参数化,比如 PriceOpen(TRADE_TYPE_ALL) 取整体均价,传 TRADE_TYPE_BUY 或 TRADE_TYPE_SELL 可单独拿某一侧。复制下面代码到 MT5 头文件,编译通过就能在 EA 里用 GetPointer 链式读取,外汇与贵金属锁仓均适用,但杠杆放大下爆仓风险偏高,参数须自行回测。

MQL5 / C++
class=class="str">"cmt">//| Class CHedgePositionInfo                                                   |
class=class="str">"cmt">//| 目标: 用于访问锁仓仓位信息的类                                          |
class=class="str">"cmt">//|          派生于 CObject 类.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHedgePositionInfo : class="kw">public CObject
  {
   class=class="str">"cmt">//--- === 数据成员 === --- 
class="kw">private:
   ENUM_HEDGE_TYPE    m_type;
   class="type">class="kw">double             m_volume;
   class="type">class="kw">double             m_price;
   class="type">class="kw">double             m_stop_loss;
   class="type">class="kw">double             m_take_profit;
   class="type">class="kw">ulong              m_magic;
   class=class="str">"cmt">//--- 对象
   CArrayLong         m_tickets;
   CSymbolInfo        m_symbol;
   CPositionInfo      m_pos_info;
   class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
   class=class="str">"cmt">//--- 构造函数/析构函数
   class="type">void               CHedgePositionInfo(class="type">void){};
   class="type">void              ~CHedgePositionInfo(class="type">void){};
   class=class="str">"cmt">//--- 初始化
   class="type">bool               Init(class="kw">const class="type">class="kw">string _symbol,class="kw">const class="type">class="kw">ulong _magic=class="num">0);
   class=class="str">"cmt">//--- get 方法
   CSymbolInfo       *Symbol(class="type">void)        {class="kw">return GetPointer(m_symbol);};
   CArrayLong        *HedgeTickets(class="type">void) {class="kw">return GetPointer(m_tickets);};
   CPositionInfo     *PositionInfo(class="type">void) {class="kw">return GetPointer(m_pos_info);};
   class="type">class="kw">ulong              Magic(class="type">void) class="kw">const   {class="kw">return m_magic;};
   class=class="str">"cmt">//--- 快速访问锁仓的整数属性的方法
   class="type">class="kw">datetime           Time(class="type">void);
   class="type">class="kw">ulong              TimeMsc(class="type">void);
   class="type">class="kw">datetime           TimeUpdate(class="type">void);
   class="type">class="kw">ulong              TimeUpdateMsc(class="type">void);
   ENUM_HEDGE_TYPE    HedgeType(class="type">void);
   class=class="str">"cmt">//--- 快速访问锁仓的双精度浮点型属性的方法
   class="type">class="kw">double             Volume(class="type">class="kw">double &_buy_volume,class="type">class="kw">double &_sell_volume);
   class="type">class="kw">double             PriceOpen(class="kw">const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   class="type">class="kw">double             StopLoss(class="kw">const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   class="type">class="kw">double             TakeProfit(class="kw">const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);
   class="type">class="kw">double             PriceCurrent(class="kw">const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL);

◍ 锁仓持仓信息类的接口与初始化边界

在 MT5 的锁仓账户体系里,想用面向对象方式批量读取某魔法码下的持仓成本,得先认清楚 CHedgePositionInfo 暴露的公共接口。它直接给了 Commission(false)、Swap()、Profit()、Margin() 四个 double 型取值器,分别对应手续费、库存费、浮动盈亏和已用预付款;另有 TypeDescription() 返回锁仓类型描述串,FormatType() 做类型到字符串的格式化,Select() 负责把目标持仓置为当前选中对象。 私有段里 AveragePrice() 用引用回传均价、基础成交量与报价成交量三个量,CheckLoadHistory() 则按周期与起点日期确认历史加载是否够用——这两个方法不对外,说明均价计算和历史完整性是内部黑盒,调用者只管拿结果。 初始化函数 Init() 第一道闸就卡账户类型:通过 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 取预付款模式,若不等于 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING 直接 Print 报错并返回 false。这意味着这套类在净头寸(netting)账户上跑不起来,外汇与贵金属锁仓环境才适配,而杠杆类品种本身波动剧烈、强平风险高,调用前务必确认账户属性。 接着用 m_symbol.Name(_symbol) 校验品种名有效性,失败同样返回 false;再读 SYMBOL_TRADE_CALC_MODE 拿到品种计算模式。开 MT5 按 F4 把这段抄进 EA,把 _magic 换成你自己订单的魔术码,就能在锁仓账户下直接探查持仓明细,非锁仓账户会卡在首道判断。

MQL5 / C++
class="type">class="kw">double Commission(class="kw">const class="type">bool _full=class="kw">false);
class="type">class="kw">double Swap(class="type">void);
class="type">class="kw">double Profit(class="type">void);
class="type">class="kw">double Margin(class="type">void);
class=class="str">"cmt">//--- 快速访问锁仓字符串型属性的方法
class="type">class="kw">string TypeDescription(class="type">void);
class=class="str">"cmt">//--- 信息方法
class="type">class="kw">string FormatType(class="type">class="kw">string &_str,class="kw">const class="type">uint _type) class="kw">const;
class=class="str">"cmt">//--- 选择
class="type">bool Select(class="type">void);
class=class="str">"cmt">//--- 状态
class="type">void StoreState(class="type">void);
class="type">bool CheckState(class="type">void);
class="kw">private:
 class=class="str">"cmt">//--- 计算方法
 class="type">bool AveragePrice(
 class="kw">const SPositionParams &_pos_params,
 class="type">class="kw">double &_avg_pr,
 class="type">class="kw">double &_base_volume,
 class="type">class="kw">double &_quote_volume
 );
 class="type">int CheckLoadHistory(ENUM_TIMEFRAMES period,class="type">class="kw">datetime start_date);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 初始化                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHedgePositionInfo::Init(class="kw">const class="type">class="kw">string _symbol,class="kw">const class="type">class="kw">ulong _magic=class="num">0)
 {
 class=class="str">"cmt">//--- 账户预付款模式
 ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
 if(margin_mode!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
  {
   Print(__FUNCTION__+": 不是锁仓模式!");
   class="kw">return class="kw">false;
  }
 if(!m_symbol.Name(_symbol))
  {
   Print(__FUNCTION__+": 交易品种没有被选择!");
   class="kw">return class="kw">false;
  }
 ENUM_SYMBOL_CALC_MODE  symbol_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(_symbol,SYMBOL_TRADE_CALC_MODE);

常见问题

用持仓遍历接口按品种筛出同标的全部仓位,再按买卖方向分组,即可分别读取两边的开仓价、手数和浮盈。
这是平台开了锁仓模式(对冲账户)的缘故,系统允许同品种多空独立存在,不会像净仓账户那样自动合并。
可以,小布盯盘的 AI 助手能直接扫描持仓,标注锁仓形态并汇总多空两边的手数、均价与盈亏,省去手动写代码。
主要有同手数对锁、不同手数对锁、多笔累加锁、部分平仓后残留锁、跨周期加仓锁五种,看属性时要逐笔核对。
初始化时必须校验持仓数组非空且同品种,否则空指针或错品种会导致读取到的开仓价和手数完全错乱。