MT5自定义仓位信息面板实战
📊

MT5自定义仓位信息面板实战

用EA与图形对象实时显示持仓属性

MQL5 编程 难度 · 进阶 2026-07-12 6 分钟阅读
#MQL5#信息面板#仓位属性#图形对象#EA开发

为什么需要自定义仓位信息面板

在MT5人工交易或观察EA持仓时,终端下方的交易栏目虽能看持仓,但信息分散、不够直观,也无法按个人习惯排版。MQL5 Cookbook前一篇用脚本获取仓位属性,需手动运行;本文进阶为EA,借助图形对象在图表右上角绘制固定面板,每次新tick自动刷新,彻底解放双手。面板用OBJ_EDIT做背景与标题,OBJ_LABEL做文字,不随价格乱跑,适合长时间盯单。

EA模板与三大生命周期函数

在MQL5向导中选Expert Advisor(template),生成默认模板含OnInit、OnDeinit、OnTick。OnInit在加载、改参数、编译、加图表、换品种周期时调用,适合做初始化与首次面板绘制;OnDeinit在移除、换账户、品种、周期、模板时调用,参数reason为去初始化原因码;OnTick在当前图表品种每次报价时触发,我们用它持续更新面板。理解这三个时机,才能知道代码何时跑、对象何时建、何时删。

✦ 布局小技巧
写代码前可先在图表用长方形标签和文本手动画出面板布局,右击对象开属性窗(或Ctrl+B)微调坐标大小,估算好后写死到数组;注意这些人工对象EA看不见,完工后要手动删掉,否则会留在图上。

常量、全局变量与对象名数组

程序头部用#define定义INFOPANEL_SIZE 14与EXPERT_NAME。全局变量保存持仓标志pos_open及13个仓位属性(symbol、magic、profit等)。两个字符串数组positionPropertyNames与positionPropertyValues各14个元素,分别存属性名与值对象的唯一名称。MQL5程序建的对象默认隐藏,在对象列表点“所有”才显示,便于区分人与程序建的图形。

#define INFOPANEL_SIZE 14
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

bool pos_open=false;
string pos_symbol="";
long pos_magic=0;
string positionPropertyNames[INFOPANEL_SIZE]={"name_pos_symbol",...};
string positionPropertyValues[INFOPANEL_SIZE]={"value_pos_symbol",...};

封装图形对象创建函数

系统ObjectCreate仅建对象,要设属性需多行。我们封装CreateEdit(OBJ_EDIT,用于背景/标题)与CreateLabel(OBJ_LABEL,用于文字)。二者接收图表ID、子窗口、名、文本、角落、字体、坐标等,成功创建后一口气设好颜色、尺寸、深度、是否可选。这样主逻辑用一行即可建对象,极大精简代码,也统一了风格。

void CreateLabel(long chart_id,int sub_window,string name,string text,ENUM_ANCHOR_POINT anchor,ENUM_BASE_CORNER corner,string font_name,int font_size,color font_color,int x_distance,int y_distance,long z_order)
{
 if(ObjectCreate(chart_id,name,OBJ_LABEL,sub_window,0,0))
 {
  ObjectSetString(chart_id,name,OBJPROP_TEXT,text);
  ObjectSetString(chart_id,name,OBJPROP_FONT,font_name);
  ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color);
  ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,anchor);
  ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner);
  ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size);
  ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance);
  ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance);
  ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
  ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order);
  ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n");
 }
}

对象删除与错误描述辅助

EA移除时必须清理自己加的对象,否则残留。DeleteObjectByName先用ObjectFind找,找到用ObjectDelete删,失败则打印错误。ErrorDescription把常见错误码(如10004重新报价、10018市场关闭)转中文,方便排错。注意原文函数名拼写为DeleteObjectByname,实际应保持与调用一致。

void DeleteObjectByname(string name)
{
 int sub_window=ObjectFind(ChartID(),name);
 if(sub_window>=0)
 {
  bool res=ObjectDelete(ChartID(),name);
  if(!res) Print("删除对象出错: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
 }
}

获取与清零仓位属性

GetPositionProperties中用PositionSelect(_Symbol)检测当前品种有无持仓,结果存pos_open。有则通过PositionGetString/Integer/Double读取全部属性;无则调ZeroPositionProperties把全局变量清为空或0。最后调SetInfoPanel刷新。这样其他函数可直接用pos_open,不必重复选仓。

void GetPositionProperties()
{
 pos_open=PositionSelect(_Symbol);
 if(pos_open)
 {
  pos_symbol=PositionGetString(POSITION_SYMBOL);
  pos_profit=PositionGetDouble(POSITION_PROFIT);
  // ... 其他属性
 }
 else ZeroPositionProperties();
 SetInfoPanel();
}

SetInfoPanel绘制与更新逻辑

SetInfoPanel计算背景、标题与14行属性的纵坐标。原文逐行写死y_prop_array,也可用for循环填充(见code 12)。随后CreateEdit画背景与抬头,循环或逐行CreateLabel画名称与值,值来自GetPropertyValue(i)。文末ChartRedraw强制重绘,否则看不到变化。面板锚点设右上角,故坐标从右上算。

⚠ 平仓不立即更新
平仓后直到下一个tick面板才变横杠。如需即时清面板,应在OnTrade而非OnTick处理,作者留待下篇。实盘挂EA时要注意这延迟,别误以为仓还在。

属性值格式化函数

GetPropertyValue按编号返回对应属性字符串。无持仓或空值(如未设SL/TP、无注释)返回“-”。有持仓则用DoubleToString按_Digits或2位小数格式化,时间用TimeToString。这样面板显示整齐,避免原始数字过长。

string GetPropertyValue(int number)
{
 string empty="-";
 if(pos_open)
 {
  switch(number)
  {
   case 0: return(pos_symbol);
   case 9: return(pos_sl!=0.0?DoubleToString(pos_sl,_Digits):empty);
   case 13: return(PositionTypeToString(pos_type));
   default: return(empty);
  }
 }
 return(empty);
}

主函数装配与去初始化提示

OnInit调GetPositionProperties做首次绘制;OnTick每次调它更新;OnDeinit打印GetDeinitReasonText(reason)并返回原因,若为REASON_REMOVE则DeleteInfoPanel清对象。GetDeinitReasonText把码转成“程序被移除”“账户切换”等中文,便于日志追踪EA为何停。

int OnInit(){ GetPositionProperties(); return(0); }
void OnTick(){ GetPositionProperties(); }
void OnDeinit(const int reason)
{
 Print(GetDeinitReasonText(reason));
 if(reason==REASON_REMOVE) DeleteInfoPanel();
}

与脚本方案及循环优化对比

前文脚本需手动运行看属性,不适合持续监控;本EA自动刷新。代码上,原文用展开写法利于初学者读,但生产环境建议用循环建14行标签,并为名称再建数组,可省数十行。MQL4无PositionSelect等函数且对象模型不同,原文代码不能直接用于MT4,需重写交易与对象部分。

✦ 学习路径
本系列前后有逻辑:先学获取属性脚本,再此面板EA,后篇用OnTrade即时更新、策略测试分析。建议顺序读,附件positionpropertiespanel.mq5可下载对照。

常见问题

写一个EA,在OnTick调用PositionSelect(_Symbol)读POSITION_SL、POSITION_PROFIT,用OBJ_LABEL创建文本对象显示在右上角面板,每次tick重绘;可参考本文SetInfoPanel与GetPropertyValue封装。
程序建的对象默认隐藏,在对象列表(Ctrl+B)窗口点“所有”按钮即可显示;人工建的不受此影响,便于区分。移除EA时需代码DeleteObjectByName清理,否则残留。
原文EA仅在OnTick更新,平仓后无新tick就不刷。应改用OnTrade事件处理函数调用GetPositionProperties,平仓瞬间即更新面板,作者下篇会讲,你可自行加OnTrade。
用switch映射reason_code,如REASON_REMOVE→“程序已被从图表上移除”,REASON_ACCOUNT→“激活不同账户”,封装GetDeinitReasonText函数在OnDeinit打印,方便查EA为何停。
不能。MT4用MQL4,无PositionSelect、PositionGetDouble及OBJ_EDIT等,持仓用OrderSelect旧模型。需重写仓位读取与图形对象部分,逻辑可参考但语法不同。