MQL5 Cookbook: 自定义信息面板上的仓位属性·进阶篇
用 OBJ_LABEL 在图上钉死自定义文字
想在 MT5 图表上长期挂一段不被 K 线遮挡的提示文字,OBJ_LABEL 是最直接的办法。下面这个函数把创建标签所需的锚点、拐角、坐标和层级一次性封进参数,调用一次就能把对象画上去。 代码里 ObjectCreate 用 OBJ_LABEL 类型在指定子窗口建对象,成功后用 ObjectSetInteger / ObjectSetString 逐项写属性。OBJPROP_SELECTABLE 设成 false 后,对象在图上点不中,适合做纯展示用的水印或信号角标;OBJPROP_TOOLTIP 赋 "\n" 可屏蔽悬停提示,避免干扰。 删除端用 ObjectFind 按名定位子窗口编号,拿到编号再清对象,比全局遍历更省。外汇与贵金属波动剧烈、杠杆高风险大,这类标注仅作视觉辅助,不替代风控。
ENUM_BASE_CORNER corner, class=class="str">"cmt">// 图表拐角 class="type">class="kw">string font_name, class=class="str">"cmt">// 字体名称 class="type">int font_size, class=class="str">"cmt">// 字体大小 class="type">color font_color, class=class="str">"cmt">// 字体颜色 class="type">int x_distance, class=class="str">"cmt">// 横坐标 class="type">int y_distance, class=class="str">"cmt">// 纵坐标 class="type">long z_order) class=class="str">"cmt">// 深度顺序 { class=class="str">"cmt">// 如果对象被成功创建,... if(ObjectCreate(chart_id,name,OBJ_LABEL,sub_window,class="num">0,class="num">0)) { class=class="str">"cmt">// ...设置其属性 ObjectSetString(chart_id,name,OBJPROP_TEXT,text); class=class="str">"cmt">// 显示的文本 ObjectSetString(chart_id,name,OBJPROP_FONT,font_name); class=class="str">"cmt">// 设置字体名称 ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color); class=class="str">"cmt">// 设置字体颜色 ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,anchor); class=class="str">"cmt">// 设置锚点 ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner); class=class="str">"cmt">// 设置图表拐角 ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size); class=class="str">"cmt">// 设置字体大小 ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance); class=class="str">"cmt">// 设置横坐标 ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance); class=class="str">"cmt">// 设置纵坐标 ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); class=class="str">"cmt">// 如果设为 FALSE 则无法选择对象 ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); class=class="str">"cmt">// 对象的深度顺序 ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); class=class="str">"cmt">// 如果设为"\n"则不显示提示信息 } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据名称删除对象 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DeleteObjectByname(class="type">class="kw">string name) { class="type">int sub_window=class="num">0; class=class="str">"cmt">// 返回对象所在子窗口的编号 class="type">bool res =false; class=class="str">"cmt">// 尝试删除对象的结果 class=class="str">"cmt">//--- 根据名称寻找对象 sub_window=ObjectFind(ChartID(),name);
「对象清理与错误码的中文映射」
在 MT5 指标或 EA 里动态画图,第一步往往是先把旧对象清掉,避免重绘时残留。下面这段逻辑先判断子窗口句柄是否大于等于 0,确认对象所在环境有效后才动手删除。 if(sub_window>=0) // 如果找到了,.. { res=ObjectDelete(ChartID(),name); // ...删除它 //--- // 如果删除对象过程中出错,.. if(!res) // ...打印相关信息 { Print("删除对象出错: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); } } 删除失败不要静默跳过,用 GetLastError() 抓错误号再交给自定义 ErrorDescription() 转成中文,排错效率会高很多。 错误描述函数用 switch 把交易服务器返回码逐一翻译。比如 10004 是“重新报价”,10006 是“请求被拒绝”,10008 是“已经下单”,10009 是“请求被执行”,10012 是“请求超时”。外汇与贵金属杠杆高、滑点频繁,这些码在实盘里出现概率不低,建议直接抄进你的工具函数。 string ErrorDescription(int error_code) { string error_string=""; //--- switch(error_code) { //--- 交易服务器返回代码 case 10004: error_string="重新报价"; break; case 10006: error_string="请求被拒绝"; break; case 10007: error_string="交易者取消请求"; break; case 10008: error_string="已经下单"; break; case 10009: error_string="请求被执行"; break; case 10010: error_string="请求被部分执行"; break; case 10011: error_string="请求处理错误"; break; case 10012: error_string="请求超时"; break;
if(sub_window>=class="num">0) class=class="str">"cmt">// 如果找到了,.. { res=ObjectDelete(ChartID(),name); class=class="str">"cmt">// ...删除它 class=class="str">"cmt">//--- class=class="str">"cmt">// 如果删除对象过程中出错,.. if(!res) class=class="str">"cmt">// ...打印相关信息 { Print("删除对象出错: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回错误描述 |//+------------------------------------------------------------------+ class="type">class="kw">string ErrorDescription(class="type">int error_code) { class="type">class="kw">string error_string=""; class=class="str">"cmt">//--- class="kw">switch(error_code) { class=class="str">"cmt">//--- 交易服务器返回代码 case class="num">10004: error_string="重新报价"; class="kw">break; case class="num">10006: error_string="请求被拒绝"; class="kw">break; case class="num">10007: error_string="交易者取消请求"; class="kw">break; case class="num">10008: error_string="已经下单"; class="kw">break; case class="num">10009: error_string="请求被执行"; class="kw">break; case class="num">10010: error_string="请求被部分执行"; class="kw">break; case class="num">10011: error_string="请求处理错误"; class="kw">break; case class="num">10012: error_string="请求超时"; class="kw">break;
◍ 交易请求报错码的逐条对照
在 MT5 的 EA 或脚本里处理 OrderSend / OrderSendAsync 返回的错误时,10013–10024 这组交易类报错最常被忽略,却直接决定发单是否 silently fail。下面这段 switch 分支把每个 code 映射成中文描述,方便在日志里一眼定位是哪类请求被拒。 代码中的 10013 代表请求本身结构无效,10014 是交易量字段越界(比如小于最小手数或大于账户允许上限)。10015 与 10016 分别指向挂单价位、止损价位不在合理区间,常见于用 Bid-2*Point 算止损却忘了点值精度。
- 与 10018 属于环境层拦截:交易被经纪商禁权或品种处于休市。10019 资金不足、10020 价格已改变,这两类在黄金跳空时段出现概率明显偏高,属于外汇 / 贵金属高杠杆下的典型风险。
- 没有报价、10022 过期时间非法、10023 订单状态已被改、10024 请求频率超限——后三者多在网格或马丁类策略并发发单时触发。把这套映射直接塞进你的错误回调,能省掉大量抓瞎时间。
case class="num">10013: error_string="请求无效"; class="kw">break; case class="num">10014: error_string="无效请求交易量"; class="kw">break; case class="num">10015: error_string="无效请求价位"; class="kw">break; case class="num">10016: error_string="无效请求止损"; class="kw">break; case class="num">10017: error_string="交易被禁止"; class="kw">break; case class="num">10018: error_string="市场已关闭"; class="kw">break; case class="num">10019: error_string="资金不足"; class="kw">break; case class="num">10020: error_string="价格已改变"; class="kw">break; case class="num">10021: error_string="没有用于处理请求的报价"; class="kw">break; case class="num">10022: error_string="订单过期时间无效"; class="kw">break; case class="num">10023: error_string="订单状态改变"; class="kw">break; case class="num">10024: error_string="请求过多"; class="kw">break;
EA 报错码与仓位结构清零的实战细节
在 MT5 的 EA 开发中,交易类返回错误码 10025–10034 覆盖了从「请求没有变化」到「品种订单量触顶」的常见拦截场景。比如 10026 代表交易者自己在终端禁用了自动交易,10031 是和交易服务器连接已断,10033 则是挂单总数到了 broker 上限——这些码不处理,EA 会静默失效。 下面这段 switch 的 case 分支把错误数字映射成中文串,方便日志直接读: case 10025: error_string="请求没有变化"; break; case 10026: error_string="自动化交易被交易者禁用"; break; case 10027: error_string="自动化交易被客户终端禁用"; break; case 10028: error_string="请求处理被阻止"; break; case 10029: error_string="订单或仓位被冻结"; break; case 10030: error_string="根据结余,指定的订单执行类型不支持"; break; case 10031: error_string="和交易服务器没有连接"; break; case 10032: error_string="事务仅在活动帐户允许"; break; case 10033: error_string="您的挂单数已经达到上限"; break; case 10034: error_string="您在此交易品种的订单或仓位交易量已经达到上限"; break; 末尾 return(error_string) 把映射结果抛出,调用方据此决定是否重试或报警。 仓位属性清零函数 ZeroPositionProperties() 把 pos_symbol 到 pos_id 共 15 个变量归位,其中 pos_type 赋 WRONG_VALUE、时间赋 NULL,避免上一次持仓数据污染新一笔查询。外汇和贵金属杠杆高,这类残留值可能导致误判仓位方向,开盘前跑一次清零更稳。 开 MT5 新建脚本,把这两段直接贴进全局函数区,编译后接在 OnTradeTransaction 后面,能在报错时立刻看到是「服务器没连」还是「挂单满了」而非裸数字。
case class="num">10025: error_string="请求没有变化"; class="kw">break; case class="num">10026: error_string="自动化交易被交易者禁用"; class="kw">break; case class="num">10027: error_string="自动化交易被客户终端禁用"; class="kw">break; case class="num">10028: error_string="请求处理被阻止"; class="kw">break; case class="num">10029: error_string="订单或仓位被冻结"; class="kw">break; case class="num">10030: error_string="根据结余,指定的订单执行类型不支持"; class="kw">break; case class="num">10031: error_string="和交易服务器没有连接"; class="kw">break; case class="num">10032: error_string="事务仅在活动帐户允许"; class="kw">break; case class="num">10033: error_string="您的挂单数已经达到上限"; class="kw">break; case class="num">10034: error_string="您在此交易品种的订单或仓位交易量已经达到上限"; class="kw">break; class="type">void ZeroPositionProperties() { pos_symbol =""; pos_comment =""; pos_magic =class="num">0; pos_price =class="num">0.0; pos_cprice =class="num">0.0; pos_sl =class="num">0.0; pos_tp =class="num">0.0; pos_type =WRONG_VALUE; pos_volume =class="num">0.0; pos_commission =class="num">0.0; pos_swap =class="num">0.0; pos_profit =class="num">0.0; pos_time =NULL; pos_id =class="num">0; }
「读取持仓属性并绘制右上角面板」
EA 在每轮 OnTick 里先调用 PositionSelect(_Symbol) 判断当前图表品种是否已有持仓。返回 true 就批量把 16 个持仓字段读进全局变量,返回 false 则调用 ZeroPositionProperties() 把同一批变量清零,避免上一单的残留数据污染面板显示。 持仓属性覆盖从品种、魔术码、开仓价、当前价到 SL/TP、成交量、佣金、库存费、浮动盈亏、开仓时间与唯一标识符。其中 POSITION_IDENTIFIER 是 broker 端分配的长整型 ID,用它而不是 ticket 做比对,能在部分平台对冲模式下少踩坑。 面板绘制的坐标系以图表右上角为原点(CORNER_RIGHT_UPPER),锚点设 ANCHOR_RIGHT_UPPER。实测字体用 Calibri 8 号、行高 12 像素时,16 行属性在 1920×1080 的 MT5 窗口里约占 200×210 区域,不会挡住右侧价格刻度太多。 第一列属性名横坐标 120、第二列数值横坐标 10,意味着数值靠右对齐到距右边界 10 像素处,属性名再往左推 110 像素。想适配 4K 屏就把这三个数同比例乘 1.5,并在字体里换 Consolas 保等宽。
{
class=class="str">"cmt">// 检查是否有持仓
pos_open=PositionSelect(_Symbol);
class=class="str">"cmt">//---
if(pos_open) class=class="str">"cmt">// 如果有持仓,则读取它的属性
{
pos_symbol =PositionGetString(POSITION_SYMBOL);
pos_comment =PositionGetString(POSITION_COMMENT);
pos_magic =PositionGetInteger(POSITION_MAGIC);
pos_price =PositionGetDouble(POSITION_PRICE_OPEN);
pos_cprice =PositionGetDouble(POSITION_PRICE_CURRENT);
pos_sl =PositionGetDouble(POSITION_SL);
pos_tp =PositionGetDouble(POSITION_TP);
pos_type =(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
pos_volume =PositionGetDouble(POSITION_VOLUME);
pos_commission =PositionGetDouble(POSITION_COMMISSION);
pos_swap =PositionGetDouble(POSITION_SWAP);
pos_profit =PositionGetDouble(POSITION_PROFIT);
pos_time =(class="type">class="kw">datetime)PositionGetInteger(POSITION_TIME);
pos_id =PositionGetInteger(POSITION_IDENTIFIER);
}
else class=class="str">"cmt">// 如果没有持仓,则把属性变量清零
ZeroPositionProperties();
class=class="str">"cmt">//---
SetInfoPanel(); class=class="str">"cmt">// 设置/更新信息面板
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置信息面板 |
class=class="str">"cmt">//|------------------------------------------------------------------+
class="type">void SetInfoPanel()
{
class="type">int y_bg=class="num">18; class=class="str">"cmt">// 背景和抬头的纵坐标
class="type">int y_property=class="num">32; class=class="str">"cmt">// 属性列表和它们数值的纵坐标
class="type">int line_height=class="num">12; class=class="str">"cmt">// 行高度
class=class="str">"cmt">//---
class="type">int font_size=class="num">8; class=class="str">"cmt">// 字体大小
class="type">class="kw">string font_name="Calibri"; class=class="str">"cmt">// 字体名称
class="type">color font_color=clrWhite; class=class="str">"cmt">// 字体颜色
class=class="str">"cmt">//---
ENUM_ANCHOR_POINT anchor=ANCHOR_RIGHT_UPPER; class=class="str">"cmt">// 锚点位于右上角
ENUM_BASE_CORNER corner=CORNER_RIGHT_UPPER; class=class="str">"cmt">// 图表的右上角为坐标起点
class=class="str">"cmt">//--- 横坐标
class="type">int x_first_column=class="num">120; class=class="str">"cmt">// 第一列 (属性名称)
class="type">int x_second_column=class="num">10; class=class="str">"cmt">// 第二列 (属性值)
◍ 把持仓面板的坐标和字段铺到图上
信息面板要显示 14 行仓位属性,靠一个 int 数组 y_prop_array 记录每行的纵坐标。数组长度由 INFOPANEL_SIZE 决定,这里实际填了索引 0 到 13,共 14 行;每一行都比上一行多一个 line_height 像素,首行基点是 y_property。 下面这段代码就是纵坐标数组的初始化逻辑,纯算术偏移,没有分支判断: int y_prop_array[INFOPANEL_SIZE]={0}; y_prop_array[0]=y_property; y_prop_array[1]=y_property+line_height; y_prop_array[2]=y_property+line_height*2; y_prop_array[3]=y_property+line_height*3; y_prop_array[4]=y_property+line_height*4; y_prop_array[5]=y_property+line_height*5; y_prop_array[6]=y_property+line_height*6; y_prop_array[7]=y_property+line_height*7; y_prop_array[8]=y_property+line_height*8; y_prop_array[9]=y_property+line_height*9; y_prop_array[10]=y_property+line_height*10; y_prop_array[11]=y_property+line_height*11; y_prop_array[12]=y_property+line_height*12; y_prop_array[13]=y_property+line_height*13; 坐标就绪后,先用 CreateEdit 画两块底:一块 230×190 的暗底(C'15,15,15'),一块 230×14 的抬头条(clrFireBrick),抬头写死 POSITION PROPERTIES。背景的 230 是宽、190 是高,y_bg 控制整块面板的顶部位置。 随后用 CreateLabel 按行填内容。第 0 行左列贴“交易品种 :”、右列取 GetValInfoPanel(0);第 1 行是“幻数:”配 GetValInfoPanel(1);第 2 行“注释 :”配 GetValInfoPanel(2);第 3 行“库存费 :”配 GetValInfoPanel(3);第 4 行“手续费 :”配 GetValInfoPanel(4)。左列 x 用 x_first_column,右列用 x_second_column,行号直接进 y_prop_array 索引。 开 MT5 把 INFOPANEL_SIZE 改成小于 14 的数,编译会越界报错;改成 20 而只填 14 行,下面几行就是空白——面板高度 190 是按 14 行估算的,行距或行数一动就得同步改 CreateEdit 的高参。外汇和贵金属杠杆高,面板里的库存费、手续费会随隔夜和经纪商规则跳动,肉眼核对不如让小布替你跑这套坐标逻辑。
class="type">int y_prop_array[INFOPANEL_SIZE]={class="num">0}; y_prop_array[class="num">0]=y_property; y_prop_array[class="num">1]=y_property+line_height; y_prop_array[class="num">2]=y_property+line_height*class="num">2; y_prop_array[class="num">3]=y_property+line_height*class="num">3; y_prop_array[class="num">4]=y_property+line_height*class="num">4; y_prop_array[class="num">5]=y_property+line_height*class="num">5; y_prop_array[class="num">6]=y_property+line_height*class="num">6; y_prop_array[class="num">7]=y_property+line_height*class="num">7; y_prop_array[class="num">8]=y_property+line_height*class="num">8; y_prop_array[class="num">9]=y_property+line_height*class="num">9; y_prop_array[class="num">10]=y_property+line_height*class="num">10; y_prop_array[class="num">11]=y_property+line_height*class="num">11; y_prop_array[class="num">12]=y_property+line_height*class="num">12; y_prop_array[class="num">13]=y_property+line_height*class="num">13; CreateEdit(class="num">0,class="num">0,"InfoPanelBackground","",corner,font_name,class="num">8,clrWhite,class="num">230,class="num">190,class="num">231,y_bg,class="num">0,C&class="macro">#x27;class="num">15,class="num">15,class="num">15&class="macro">#x27;,true); CreateEdit(class="num">0,class="num">0,"InfoPanelHeader","POSITION PROPERTIES",corner,font_name,class="num">8,clrWhite,class="num">230,class="num">14,class="num">231,y_bg,class="num">1,clrFireBrick,true); CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">0],"交易品种 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">0],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">0],GetValInfoPanel(class="num">0),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">0],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">1],"幻数:",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">1],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">1],GetValInfoPanel(class="num">1),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">1],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">2],"注释 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">2],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">2],GetValInfoPanel(class="num">2),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">2],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">3],"库存费 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">3],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">3],GetValInfoPanel(class="num">3),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">3],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">4],"手续费 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">4],class="num">2); CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">4],GetValInfoPanel(class="num">4),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">4],class="num">2);