MQL5获取仓位属性实战
用脚本对话框查看持仓明细与多品种遍历
为什么需要专门获取仓位属性
在MT5的人工或程序化交易中,随时掌握持仓的幻数、注释、开仓价、止损获利、手续费与库存费等信息,是风险监控和策略调试的基础。MQL5虽然提供了交易观察窗口,但当我们需要把特定属性批量导出、或在脚本中做条件判断时,就必须用代码主动获取。本文的脚本把信息以MessageBox对话框呈现,比打印到日志更直观,也便于教学演示。
前一篇MQL5 Cookbook文章使用三种打印模式输出信息,而本文更进一步:允许用户通过外部参数选择只查当前图表品种,还是遍历终端所有品种,并用图形对话框代替终端日志,交互性更好。理解本文后,你也能把同样的逻辑移植到EA的信息面板中。
脚本总体结构与外部参数设计
程序开头用#property定义脚本元信息,并用#define把MQL5InfoString(MQL5_PROGRAM_NAME)赋值给SCRIPT_NAME,便于在代码中统一引用脚本名。核心是一个枚举ENUM_SYMBOLS_MODE,提供CURRENT_SYMBOL(当前交易品种)和ALL_SYMBOLS(所有交易品种)两个选项。将其作为input参数mode,用户在脚本属性框的下拉列表里就能直接选择,注释会显示为选项文本。
全局变量区声明了pos_magic、pos_symbol、pos_volume等十余个变量,用于暂存从持仓对象读取的属性。把它们放在函数外,是为了让PrintPositionProperties、GetPositionProperties等自定义函数都能访问,避免重复传参。
#property copyright "Copyright 2012, http://tol64.blogspot.com"
#property link "http://tol64.blogspot.com"
#property description "email: hello.tol64@gmail.com"
#property version "1.0"
#property script_show_inputs
#define SCRIPT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)
enum ENUM_SYMBOLS_MODE
{
CURRENT_SYMBOL =0,
ALL_SYMBOLS =1
};
input ENUM_SYMBOLS_MODE mode=CURRENT_SYMBOL;
long pos_magic=0; string pos_symbol=""; string pos_comment=""; double pos_swap=0.0; double pos_commission=0.0; double pos_price=0.0; double pos_cprice=0.0; double pos_profit=0.0; double pos_volume=0.0; double pos_sl=0.0; double pos_tp=0.0; datetime pos_time=NULL; long pos_id=0; ENUM_POSITION_TYPE pos_type=NULL;
主函数OnStart极其简洁,只调用PrintPositionProperties()。所有分支逻辑都封装在被调函数里,符合MQL5模块化编程习惯,也方便单步调试。
void OnStart()
{
PrintPositionProperties();
}
核心函数PrintPositionProperties的工作机制
该函数先定义局部变量err用于错误捕获,然后根据mode走两个分支。CURRENT_SYMBOL分支针对当前图表品种调用PositionSelect(_Symbol),若返回真说明有持仓,就调用GetPositionProperties读属性并弹对话框;若返回假则用GetLastError判断是无持仓还是真错误,并给相应提示。ALL_SYMBOLS分支则用PositionsTotal获得终端总持仓数,用for循环配合PositionGetSymbol(i)逐一取出品种名,再PositionSelect后读取并弹窗,对话框带重试/继续/取消按钮。
void PrintPositionProperties()
{
int err=0;
if(mode==CURRENT_SYMBOL)
{
}
if(mode==ALL_SYMBOLS)
{
}
}
在CURRENT_SYMBOL分支内部,PositionSelect是唯一参数_Symbol(预定义当前品种变量)。注意:在净持仓账户(默认MT5账户)中,一个品种只能有一条持仓,因此PositionSelect只取该净仓。若券商开启对冲,同品种可多仓,此写法只认第一仓,需改用按持仓编号遍历,后文FAQ会说明。
if(PositionSelect(_Symbol))
{
GetPositionProperties();
MessageBox("交易品种 : "+pos_symbol+"\n"+
"注释 : "+pos_comment+"\n"+
"幻数 : "+IntegerToString(pos_magic)+"\n"+
"建仓价位 : "+DoubleToString(pos_price,_Digits)+"\n"+
"当前价位 : "+DoubleToString(pos_cprice,_Digits)+"\n"+
"止损价位 : "+DoubleToString(pos_sl,_Digits)+"\n"+
"获利价位 : "+DoubleToString(pos_tp,_Digits)+"\n"+
"类型 : "+PositionTypeToString(pos_type)+"\n"+
"交易量 : "+DoubleToString(pos_volume,2)+"\n"+
"手续费 : "+DoubleToString(pos_commission,2)+"\n"+
"库存费 : "+DoubleToString(pos_swap,2)+"\n"+
"利润 : "+DoubleToString(pos_profit,2)+"\n"+
"时间 : "+TimeToString(pos_time)+"\n"+
"编号 : "+IntegerToString(pos_id)+"",
"消息框",MB_ICONASTERISK);
return;
}
else
{
err=GetLastError();
if(err>0)
{
MessageBox("当选择("+_Symbol+")仓位时出错 ("+IntegerToString(err)+") !\n\n"+
"也许该交易品种没有持仓. 如果不是这种情况,请重试.",
"错误",
MB_ICONWARNING);
return;
}
}
属性读取与类型转换辅助函数
GetPositionProperties用PositionGetString、PositionGetInteger、PositionGetDouble分别读取字符串、整数、双精度属性。POSITION_TYPE返回的是枚举整数,需要用PositionTypeToString转为“买入/卖出”中文,提高可读性。这种抽取公共函数的做法,避免在每个分支重复写十几行读取代码,也降低出错概率。
void GetPositionProperties()
{
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 =(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 =(datetime)PositionGetInteger(POSITION_TIME);
pos_id =PositionGetInteger(POSITION_IDENTIFIER);
}
string PositionTypeToString(int position_type)
{
string str="";
if(position_type==0) { str="买入"; }
if(position_type==1) { str="卖出"; }
return(str);
}
遍历所有品种持仓的实现细节
ALL_SYMBOLS分支先取pos_total=PositionsTotal(),再for(i=0;i<pos_total;i++)。每次循环用PositionGetSymbol(i)拿品种名,SymbolInfoInteger取小数位,然后PositionSelect。对话框用MB_CANCELTRYCONTINUE标志,用户可取消、重试或继续。标题行显示“全部/当前持仓”计数,便于定位。这种方式本质是把终端持仓池逐个激活到当前上下文再读属性。
int digits=0;
int mb_res=-1;
int pos_total=PositionsTotal();
for(int i=0; i<pos_total; i++)
{
ResetLastError();
pos_symbol=PositionGetSymbol(i);
digits=(int)SymbolInfoInteger(pos_symbol,SYMBOL_DIGITS);
if(PositionSelect(pos_symbol))
{
GetPositionProperties();
mb_res=MessageBox("全部/当前持仓: "+IntegerToString(pos_total)+"/"+IntegerToString(i+1)+"\n"+
"---------------------------------\n"+
"交易品种: "+pos_symbol+"\n"+
"注释: "+pos_comment+"\n"+
"幻数: "+IntegerToString(pos_magic)+"\n"+
"建仓价位: "+DoubleToString(pos_price,digits)+"\n"+
"当前价位: "+DoubleToString(pos_cprice,digits)+"\n"+
"止损价位: "+DoubleToString(pos_sl,digits)+"\n"+
"获利价位: "+DoubleToString(pos_tp,digits)+"\n"+
"类型: "+PositionTypeToString(pos_type)+"\n"+
"交易量: "+DoubleToString(pos_volume,2)+"\n"+
"手续费: "+DoubleToString(pos_commission,2)+"\n"+
"库存费: "+DoubleToString(pos_swap,2)+"\n"+
"利润: "+DoubleToString(pos_profit,2)+"\n"+
"时间: "+TimeToString(pos_time)+"\n"+
"编号: "+IntegerToString(pos_id)+"",
"消息框",MB_CANCELTRYCONTINUE|MB_ICONASTERISK);
if(mb_res==IDCANCEL) { /* 终止 */ }
}
}
实操步骤:从编译到运行
第一步,在MetaEditor新建脚本,粘贴上述代码保存为printpositionproperties.mq5并编译。第二步,MT5按F9开仓(如AUDUSD买0.1手),设好SL/TP。第三步,把脚本拖到图表,弹窗中mode选“当前交易品种”点确定,即看到图2式对话框;若无可持仓则图3提醒。第四步,再开EURUSD仓,mode改“所有交易品种”,逐屏看图4式对话框,可用按钮控制流程。
- 打开MetaEditor,文件-新建-脚本,清空模板
- 复制文中代码1至8组合进文件,注意全局变量在函数外
- 编译无错后,MT5导航器找到脚本拖入图表
- 外部参数选模式,确认后观察对话框输出
与日志打印及EA面板方案的对比
相比前篇用Print三种模式输出到日志,本文对话框方案不需打开日志标签,信息集中且支持交互按钮,适合演示。但脚本每次需手动拖入,无法自动刷新。作者后续文章改用EA+图形对象做信息面板,OnTrade触发刷新,更适合长期监控。若只需一次性查账,脚本最轻量;若常驻监控,应升级为EA。三者底层都依赖PositionSelect/PositionGet系列,区别在于触发方式与呈现载体。
另外,MQL5标准库CTrade和CPositionInfo也可读仓,但本文用原生函数更透明,便于初学者理解持仓池上下文切换机制。生产环境可用标准库减少样板代码,但学习阶段建议手写一遍。
账户类型与多仓处理的注意事项
原文评论区有用户指出:同一品种开两笔卖单,当前品种分支只识别第一笔。这是因为MT5默认净仓账户每品种仅一仓;若券商允许对冲(如部分国际经纪商),同品种多仓并存,PositionSelect(_Symbol)仅激活其中一笔(通常是第一笔)。要全抓,需改ALL_SYMBOLS逻辑并加symbol过滤,或用PositionGetTicket配合历史与当前池遍历。本文代码在净仓环境完全正确,对冲需扩展。