MQL5 Cookbook: 自定义信息面板上的仓位属性·综合运用
📊

MQL5 Cookbook: 自定义信息面板上的仓位属性·综合运用

(3/3)·手动跑脚本看仓位太笨?用 EA + 图形对象做常驻面板,订单一来就自己更新

案例拆解 第 3/3 篇
还在每次想看仓位细节就手动跑脚本?那是前一篇的权宜之法。本篇直接让 EA 在图表上画一块常驻面板,新报价一来就自己刷新,你只管盯盘。

持仓信息面板的逐行标签铺设

在 MT5 的自定义信息面板里,持仓关键字段是靠连续调用 CreateLabel 铺出来的。下面这段从建仓价位一直排到类型标签,覆盖了单子最核心的 10 个观察维度:建仓价、当前价、利润、交易量、止损价、获利价、时间、编号、类型。 每一行都是「名称标签 + 数值标签」成对出现:名称固定在左列 x_first_column,数值取右列 x_second_column,纵向坐标由 y_prop_array 索引控制。索引从 5 到 13 连续递增,说明面板布局是严格按数组顺序向下排的,改一处 y 偏移就可能让整列错位。 最后一行只写了类型名称标签(索引 13),没跟数值标签,原文在此截断。若你要补全,得补一行 pos_prop_values[13] 配合 GetValInfoPanel(13) 调用,否则面板右侧这一行会空着。外汇与贵金属杠杆高,面板数值仅作盯盘参考,实际下单仍以终端成交为准。

MQL5 / C++
  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);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">5],"建仓价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">5],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">5],GetValInfoPanel(class="num">5),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">5],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">6],"当前价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">6],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">6],GetValInfoPanel(class="num">6),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">6],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">7],"利润 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">7],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">7],GetValInfoPanel(class="num">7),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">7],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">8],"交易量 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">8],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">8],GetValInfoPanel(class="num">8),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">8],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">9],"止损价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">9],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">9],GetValInfoPanel(class="num">9),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">9],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">10],"获利价位 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">10],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">10],GetValInfoPanel(class="num">10),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">10],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">11],"时间 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">11],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">11],GetValInfoPanel(class="num">11),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">11],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">12],"编号 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">12],class="num">2);
  CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">12],GetValInfoPanel(class="num">12),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">12],class="num">2);
class=class="str">"cmt">//---
  CreateLabel(class="num">0,class="num">0,pos_prop_names[class="num">13],"类型 :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[class="num">13],class="num">2);

◍ 持仓属性逐行回填与取值逻辑

信息面板每一行的纵向坐标靠一个循环预填:第 0 行直接用基准 y_property,其余行按 line_height 递增偏移,这样后续标签绘制不会重叠。 CreateLabel 那行把第 13 号属性画到第二列指定坐标后,立刻调 ChartRedraw 强制重画,避免 MT5 图表缓存导致标签迟滞。外汇与贵金属杠杆高,面板数值仅作辅助参考,实际平仓仍以券商成交回报为准。 GetPropertyValue 用 switch 按编号返回持仓字段:0 是品种名,1 是 magic 号转整型字符串,2 无注释时回 '-',3~4 的 swap 与 commission 固定保留两位小数,5~6 的开仓价与当前价跟随 _Digits 精度。复制这段代码到 EA 里,把 INFOPANEL_SIZE 改成你自己的行数就能直接复用。

MQL5 / C++
CreateLabel(class="num">0,class="num">0,pos_prop_values[class="num">13],GetValInfoPanel(class="num">13),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[class="num">13],class="num">2);
class=class="str">"cmt">//---
  ChartRedraw(); class=class="str">"cmt">// 重画图表
  }
class=class="str">"cmt">//--- 使用信息面板中每一行的坐标填充数组
  for(class="type">int i=class="num">0; i<INFOPANEL_SIZE; i++)
  {
    if(i==class="num">0) y_prop_array[i]=y_property;
    else     y_prop_array[i]=y_property+line_height*i;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回仓位属性值的字符串                                                            | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GetPropertyValue(class="type">int number)
  {
class=class="str">"cmt">//--- 没有持仓或某属性的符号
class=class="str">"cmt">//    比如没有注释,止损价位或者获利价位
  class="type">class="kw">string empty="-";
class=class="str">"cmt">//--- 如果有持仓,返回所请求属性的值
  if(pos_open)
    {
     class="kw">switch(number)
       {
       case class="num">0  : class="kw">return(pos_symbol);                                                            class="kw">break;
       case class="num">1  : class="kw">return(IntegerToString((class="type">int)pos_magic));                                       class="kw">break;
       class=class="str">"cmt">//--- 如有注释返回注释的值,否则返回没有注释的符号
       case class="num">2  : class="kw">return(pos_comment!="" ? pos_comment : empty);                                 class="kw">break;
       case class="num">3  : class="kw">return(DoubleToString(pos_swap,class="num">2));                                            class="kw">break;
       case class="num">4  : class="kw">return(DoubleToString(pos_commission,class="num">2));                                      class="kw">break;
       case class="num">5  : class="kw">return(DoubleToString(pos_price,_Digits));                                     class="kw">break;
       case class="num">6  : class="kw">return(DoubleToString(pos_cprice,_Digits));                                    class="kw">break;
       case class="num">7  : class="kw">return(DoubleToString(pos_profit,class="num">2));                                          class="kw">break;
       case class="num">8  : class="kw">return(DoubleToString(pos_volume,class="num">2));                                          class="kw">break;

「面板清理与EA生命周期钩子」

上面这段 switch 的收尾逻辑很直白:case 9 到 13 分别把止损、止盈、开仓时间、订单 ticket 和持仓类型转成字符串返回,default 一律给空串。若当前无任何持仓,函数直接返回 '-',面板格子不会显示脏数据。 DeleteInfoPanel() 负责把背景、抬头以及 INFOPANEL_SIZE 个属性名和值对象逐个用 DeleteObjectByName 清除,最后调 ChartRedraw() 强制重绘。实盘里若面板对象没删干净,MT5 图表会残留文本框,肉眼就能看出 EA 卸载不彻底。 OnInit 只做一件事:调 GetPositionProperties() 读属性并画面板,返回 0 表示初始化成功。OnDeinit 则先打印去初始化原因文本,当 reason 等于 REASON_REMOVE(即从图表移除 EA)时才调 DeleteInfoPanel() 清场——如果是换周期或重编译触发去初始化,面板对象会保留在图上。 外汇与贵金属杠杆高、滑点跳空频繁,EA 卸载时残留对象可能干扰后续手动下单,建议自己在 MT5 里挂上 EA 再拖离图表,确认对象树里 InfoPanel 前缀全清空。

MQL5 / C++
   case class="num">9  : class="kw">return(pos_sl!=class="num">0.0 ? DoubleToString(pos_sl,_Digits) : empty); class="kw">break;
   case class="num">10 : class="kw">return(pos_tp!=class="num">0.0 ? DoubleToString(pos_tp,_Digits) : empty); class="kw">break;
   case class="num">11 : class="kw">return(TimeToString(pos_time,TIME_DATE|TIME_MINUTES));      class="kw">break;
   case class="num">12 : class="kw">return(IntegerToString((class="type">int)pos_id));                      class="kw">break;
   case class="num">13 : class="kw">return(PositionTypeToString(pos_type));                    class="kw">break;

   class="kw">default : class="kw">return(empty);
   }
   }
class=class="str">"cmt">//---
class=class="str">"cmt">//如果没有持仓,返回缺少持仓的符号 "-"
   class="kw">return(empty);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 删除信息面板                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteInfoPanel()
  {
  DeleteObjectByName("InfoPanelBackground");   class=class="str">"cmt">// 删除面板背景
  DeleteObjectByName("InfoPanelHeader");       class=class="str">"cmt">// 删除面板抬头
class=class="str">"cmt">//--- 删除仓位属性及其值
  for(class="type">int i=class="num">0; i<INFOPANEL_SIZE; i++)
    {
    DeleteObjectByName(pos_prop_names[i]);     class=class="str">"cmt">// 删除属性
    DeleteObjectByName(pos_prop_values[i]);    class=class="str">"cmt">// 删除值
    }
class=class="str">"cmt">//---
  ChartRedraw(); class=class="str">"cmt">// 重画图表
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 读取属性并设置面板
  GetPositionProperties();
class=class="str">"cmt">//---
  class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA去初始化函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- 在日志中打印去初始化原因
  Print(GetDeinitReasonText(reason));
class=class="str">"cmt">//--- 当从图表上移除时
  if(reason==REASON_REMOVE)
    class=class="str">"cmt">//--- 从图表上删除所有有关信息面板的对象
    DeleteInfoPanel();
  }

把EA卸载原因翻译成人话

EA在MT5图表上停止运行,背后总有具体触发源。系统通过去初始化原因码(0~9)告知你到底是手动移除、重编译,还是账户切换。 下面这段处理函数把 reason_code 映射成可读文本,直接挂到面板或日志里就能看到。外汇与贵金属品种换账户或换模板时EA可能被静默卸载,属于高风险环境下的常见坑。 [CODE] 逐行拆解: string GetDeinitReasonText(int reason_code) — 接收整型原因码,返回描述串 string text=""; — 先置空结果 switch(reason_code) — 按码分支 case REASON_PROGRAM: 0,EA自己调ExpertRemove()停的 case REASON_REMOVE: 1,程序从图表被移除 case REASON_RECOMPILE: 2,EA被重新编译 case REASON_CHARTCHANGE: 3,品种或周期变了 case REASON_CHARTCLOSE: 4,图表关了 case REASON_PARAMETERS: 5,输入参数被改 case REASON_ACCOUNT: 6,切了账户 case REASON_TEMPLATE: 7,套了别的模板 case REASON_INITFAILED: 8,OnInit返回零初始化失败 case REASON_CLOSE: 9,终端关闭 default: 原因不明 开MT5把这个函数塞进自己的EA,在OnDeinit里打印GetDeinitReasonText(reason),下次策略莫名离线就能立刻定位。

MQL5 / C++
class="type">class="kw">string GetDeinitReasonText(class="type">int reason_code)
  {
   class="type">class="kw">string text="";
class=class="str">"cmt">//---
   class="kw">switch(reason_code)
     {
      case REASON_PROGRAM :      class=class="str">"cmt">// class="num">0
         text="EA 交易通过调用 ExpertRemove()函数停止工作.";   class="kw">break;
      case REASON_REMOVE :        class=class="str">"cmt">// class="num">1
         text="&class="macro">#x27;"+EXPERT_NAME+"&class="macro">#x27; 程序已被从图表上移除.";                      class="kw">break;
      case REASON_RECOMPILE :    class=class="str">"cmt">// class="num">2
         text="&class="macro">#x27;"+EXPERT_NAME+"&class="macro">#x27; 程序已经被重新编译.";                                        class="kw">break;
      case REASON_CHARTCHANGE : class=class="str">"cmt">// class="num">3
         text="图表的交易品种或周期已经改变.";                                                      class="kw">break;
      case REASON_CHARTCLOSE :   class=class="str">"cmt">// class="num">4
         text="图表被关闭.";                                                                       class="kw">break;
      case REASON_PARAMETERS :   class=class="str">"cmt">// class="num">5
         text="输入参数被用户改变.";                                                   class="kw">break;
      case REASON_ACCOUNT :      class=class="str">"cmt">// class="num">6
         text="激活了一个不同的账户.";                                                   class="kw">break;
      case REASON_TEMPLATE :     class=class="str">"cmt">// class="num">7
         text="应用了一个不同的图表模板.";                                                class="kw">break;
      case REASON_INITFAILED :   class=class="str">"cmt">// class="num">8
         text="有标志表明OnInit() 处理函数返回零值.";                 class="kw">break;
      case REASON_CLOSE :        class=class="str">"cmt">// class="num">9
         text="终端被关闭.";                                                                       class="kw">break;
      class="kw">default : text="原因不明.";
     }
class=class="str">"cmt">//---
  }

◍ 文本返回前的收口处理

这段代码片段出现在指标或脚本的字符串拼接逻辑末尾,仅有一行 return text;,作用是把已组装好的字符串直接返回给调用方。 从工程角度看,它意味着前面所有格式化、条件判断已在 text 变量中定型,此处不做二次加工。若你在 MT5 自定义指标里看到类似收尾,可直接在 return 前插一行 Print(text); 验证输出内容是否符合预期。 外汇与贵金属市场高风险,任何输出逻辑改动都应在策略测试器内先跑历史数据确认无报错。

MQL5 / C++
  class="kw">return text;
}

「记住这一条就够了」

这套仓位属性面板的核心源码在附带的 positionpropertiespanel.mq5 里,文件 81.93 KB,直接丢进 MT5 的 MetaEditor 就能编译跑起来。 作者把函数拆成独立模块,后面几篇 Cookbook 会复用或改写,比如策略测试器里分析仓位、获取全部仓位属性,都是同一套底子往上加东西。 如果你刚上手 MQL5,别跳着看,顺着系列顺序读,逻辑链才接得上;有经验的可以从新发布的改版文切入。外汇和贵金属波动剧烈,高杠杆下任何面板都只是辅助,信号误读可能直接吃掉本金。 打开 MT5 把那個 mq5 加载到图表,勾选只看当前品种还是全部品种,先确认你的终端能正常吐出属性再谈二次开发。

让小布替你盯这块面板
这些仓位属性的实时呈现逻辑,小布盯盘的 AIGC 已内置到对应品种页,打开就能看到结构化诊断,把重复劳动交给小布,你专注决策。

常见问题

OnInit 在加载、改参数、切品种或周期时都会触发,适合做对象和数组的初始化;脚本的 OnStart 只跑一次,没有持续生命周期。
可以,小布盯盘对应品种页已用 AIGC 聚合了仓位方向、手数、浮盈等字段,不必自己写 EA 也能获得类似视图。
EA 只能通过代码识别自己创建的对象 ID,手动画的布局对象不在其管理范围内,不删会留在图表上干扰显示。
可写 GetDeinitReasonText 把传入的 reason 编号映射到字符串,便于在日志或面板里提示当前是被移除还是切了账户。
两者都不随价格移动,长方形标签更易控边框和填充,文本对象更轻量;本例用前者做抬头背景、后者做字段值。