MQL5 酷客宝典: 读取持有锁仓仓位的属性·进阶篇
(2/3)· 锁仓账户下合计仓位不止买/卖两种状态,误判类型会让风控与对冲脚本直接算错净敞口
锁仓时间的取数与初始化校验
锁仓类在初始化阶段就拦掉了非外汇品种。若 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 分支。
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,可据此验证你的仓位聚合有没有漏算。
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 才有效,否则会漏算部分平仓费的返还。
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 会变小甚至转负,这时候你的‘成本’其实是平台返给你的,别直接当亏损扣掉。
{
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 同一账户跑多套策略互不误伤,外汇与贵金属杠杆高,错平他人单子可能引发连锁风险。
{
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 图直接跑,就能对照日志验证你本地仓位的读取逻辑有没有偏差。
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