在 MetaTrader 5 里使用 HedgeTerminal (对冲终端) 面板进行双向交易和仓位对冲, 第二部分·进阶篇
(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 出来的条数是否和账户历史对得上。
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。外汇与贵金属品种波动大、杠杆高,这类查询只用于复盘与风控核对,实盘信号须自行验证。
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 过滤条件可能漏掉真实仓位。
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) 取实际成交价。外汇与贵金属对冲操作本身杠杆高、滑点风险大,这类扫描脚本只在策略测试器或低仓位验证环境跑,别直接挂实盘大资金。
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 把方向、入场价、滑点、末笔成交号和佣金总和打印成一行。外汇与贵金属杠杆高,滑点和佣金会直接啃掉波段利润,跑这段代码前先在策略测试器用历史数据验证输出是否符合预期。
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 去重的关键。
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">// 空头仓位带止盈