MT5自定义仓位信息面板实战
用EA与图形对象实时显示持仓属性
为什么需要自定义仓位信息面板
在MT5人工交易或观察EA持仓时,终端下方的交易栏目虽能看持仓,但信息分散、不够直观,也无法按个人习惯排版。MQL5 Cookbook前一篇用脚本获取仓位属性,需手动运行;本文进阶为EA,借助图形对象在图表右上角绘制固定面板,每次新tick自动刷新,彻底解放双手。面板用OBJ_EDIT做背景与标题,OBJ_LABEL做文字,不随价格乱跑,适合长时间盯单。
EA模板与三大生命周期函数
在MQL5向导中选Expert Advisor(template),生成默认模板含OnInit、OnDeinit、OnTick。OnInit在加载、改参数、编译、加图表、换品种周期时调用,适合做初始化与首次面板绘制;OnDeinit在移除、换账户、品种、周期、模板时调用,参数reason为去初始化原因码;OnTick在当前图表品种每次报价时触发,我们用它持续更新面板。理解这三个时机,才能知道代码何时跑、对象何时建、何时删。
常量、全局变量与对象名数组
程序头部用#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强制重绘,否则看不到变化。面板锚点设右上角,故坐标从右上算。
属性值格式化函数
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,需重写交易与对象部分。