MQL5获取仓位属性实战
📊

MQL5获取仓位属性实战

用脚本对话框查看持仓明细与多品种遍历

MQL5 编程 难度 · 入门 2026-05-15 8 分钟阅读
#MQL5#仓位属性#脚本开发#PositionSelect

为什么需要专门获取仓位属性

在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);
  }
⚠ 常见坑
PositionGetInteger返回的是long,转枚举或datetime时要显式转换;DoubleToString的小数位用_Symbol的_Digits或SymbolInfoInteger取,否则不同品种显示位数错乱。

遍历所有品种持仓的实现细节

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式对话框,可用按钮控制流程。

✦ 调试建议
若对话框一闪而过,检查是否无持仓或账户为对冲却用当前品种分支;可在OnStart首行加Print(mode)确认参数传入。

与日志打印及EA面板方案的对比

相比前篇用Print三种模式输出到日志,本文对话框方案不需打开日志标签,信息集中且支持交互按钮,适合演示。但脚本每次需手动拖入,无法自动刷新。作者后续文章改用EA+图形对象做信息面板,OnTrade触发刷新,更适合长期监控。若只需一次性查账,脚本最轻量;若常驻监控,应升级为EA。三者底层都依赖PositionSelect/PositionGet系列,区别在于触发方式与呈现载体。

另外,MQL5标准库CTrade和CPositionInfo也可读仓,但本文用原生函数更透明,便于初学者理解持仓池上下文切换机制。生产环境可用标准库减少样板代码,但学习阶段建议手写一遍。

账户类型与多仓处理的注意事项

原文评论区有用户指出:同一品种开两笔卖单,当前品种分支只识别第一笔。这是因为MT5默认净仓账户每品种仅一仓;若券商允许对冲(如部分国际经纪商),同品种多仓并存,PositionSelect(_Symbol)仅激活其中一笔(通常是第一笔)。要全抓,需改ALL_SYMBOLS逻辑并加symbol过滤,或用PositionGetTicket配合历史与当前池遍历。本文代码在净仓环境完全正确,对冲需扩展。

⚠ 对冲账户提醒
在对冲账户跑本文CURRENT_SYMBOL模式,会漏掉同品种其他仓。应循环PositionsTotal并用PositionGetSymbol过滤目标品种,或调用OrderGet/HistoryOrder读多单。

常见问题

新建脚本,用input枚举选模式,OnStart调用自定义函数;在函数里用PositionSelect(_Symbol)激活当前仓,再用PositionGetString/Integer/Double读取POSITION_*属性,最后MessageBox弹出。完整代码见本文code 1-7。
原文CURRENT_SYMBOL分支基于净仓假设。对冲账户需改为遍历PositionsTotal(),用PositionGetSymbol(i)取品种名并if过滤目标符号,再GetPositionProperties读每笔;或按持仓标识符循环。不能只靠PositionSelect(_Symbol)。
PositionSelect(symbol)按品种名激活该品种当前上下文持仓(净仓为一,对冲为某笔);PositionGetSymbol(index)按终端持仓池序号返回品种名,常用于遍历全部持仓。两者常配合:先GetSymbol拿名,再Select激活后读属性。
本文为技术教程不涉及发布。一般来说需在MQL5社区注册卖家,通过实名与程序审核,提交源代码与说明,官方查病毒与违规API调用,通过后设置价格与激活次数方可上架。
用本文PositionTypeToString函数:position_type==0返回“买入”,==1返回“卖出”;也可直接用EnumToString(POSITION_TYPE)拿英文枚举名,再自行映射中文。