图表上的历史仓位及其盈利/亏损图指标(基础篇)
📘

图表上的历史仓位及其盈利/亏损图指标(基础篇)

第 1/3 篇

◍ 把历史仓位画进价格图

MT5 里有一类指标不画均线也不画通道,只干一件事:把账户里已经平掉的历史仓位,连同每笔的盈利亏损,直接投影到对应 K 线位置上。 这类工具属于交易类扩展,核心由三个对象构成:单笔仓位数据、历史仓位列表、以及把浮盈浮亏随时间连成曲线的绘图层。 以 2024 年 7 月 24 日发布的某公开版本为例,该指标在社区累计被加载 1184 次、评论 10 条,说明手动复盘人群对它有一定刚需。 外汇与贵金属杠杆高、滑点随机,历史盈利分布不代表未来;用这类图做验证时,先开 MT5 的「账户历史」核对成交时间,再叠加指标看盈亏曲线是否和你的记忆一致。

「历史仓位没有现成函数可查」

在 MT5 里,未平仓位和对冲账户的独立仓位都有直接接口可取:净额账户用 PositionSelect() 按品种名选中,对冲账户则先用 PositionsTotal() 拿数量,再靠 PositionGetTicket(i) 取索引对应的单号,最后 PositionSelectByTicket() 定位。这套逻辑跑通很容易,但已平掉的历史仓位就断了——平台没有专门处理历史仓位的函数。 破局点在于每个仓位都有唯一 ID,且这个 ID 被记在影响该仓位的每笔成交上(开仓、改仓、平仓都带)。用 HistorySelect(0, TimeCurrent()) 拉出成交列表,配合 HistoryDealsTotal() 与 HistoryDealGetTicket(i) 遍历,再从每笔成交用 HistoryDealGetInteger(ticket, DEAL_POSITION_ID) 取出参与的仓位 ID,就能反向拼出历史仓位。 具体做法是在遍历成交时,检查该仓位 ID 是否已在自建列表:没有就新建仓位对象,有就追加当前成交到它的成交清单。跑完整个 HistoryDealsTotal() 循环,所有历史仓位及其对应成交就还原了。 落地时建议建三个类:成交类(存仓位标识与属性)、仓位类(存参与成交列表与自身属性)、历史仓位列表类(按属性筛选)。借助标准库的 CObject 派生指针数组,重载 Compare() 并按 m_sort_mode 切换排序字段,搜索效率会高很多。外汇与贵金属杠杆高,历史回测仅反映过去,实盘仍可能因滑点、点差突变而偏离。 下面这段是从原文抽出的核心调用骨架,直接在 MT5 新建脚本粘进去就能看现象:

MQL5 / C++
&nbsp;&nbsp; <span class="keyword">if</span>(<span class="functions">PositionSelect</span>(symbol_name))
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">/*
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Work with selected position data
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetdouble" target="_blank">PositionGetDouble()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetinteger" target="_blank">PositionGetInteger()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetstring" target="_blank">PositionGetString()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="type">int</span> total=<span class="functions">PositionsTotal</span>();
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=total-<span class="number">class="num">1</span>;i&gt;=<span class="number">class="num">0</span>;i--)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">ulong</span> ticket=<span class="functions">PositionGetTicket</span>(i);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(ticket==<span class="number">class="num">0</span> || !<span class="functions">PositionSelectByTicket</span>(ticket))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">continue</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">/*
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Work with selected position data
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetdouble" target="_blank">PositionGetDouble()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetinteger" target="_blank">PositionGetInteger()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="/zh/docs/trading/positiongetstring" target="_blank">PositionGetString()</a>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">if</span>(<span class="functions">HistorySelect</span>(<span class="number">class="num">0</span>,<span class="functions">TimeCurrent</span>()))
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> total=<span class="functions">HistoryDealsTotal</span>();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i&lt;total;i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">ulong</span> ticket=<span class="functions">HistoryDealGetTicket</span>(i);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(ticket==<span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">continue</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">long</span> pos_id=<span class="functions">HistoryDealGetInteger</span>(ticket,<span class="macro">DEAL_POSITION_ID</span>); <span class="comment">class=class="str">"cmt">// ID of a position a deal participated in</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">/*
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Work with selected deal data
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HistoryDealGetDouble();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HistoryDealGetInteger();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HistoryDealGetString();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PositionInfoIndicator.mq5 |</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Copyright class="num">2023, MetaQuotes Ltd. |</span>

用填充图把持仓盈亏画进副图

想在 MT5 副窗直接看当前持仓的浮动盈亏带,而不是切到交易面板盯数字,可以写一个独立指标,把盈利区填绿、亏损区填红。 下面这段声明决定了指标骨架:用 indicator_separate_window 放到副图,indicator_buffers 设 2 个缓冲区但只 plot 1 个图,类型选 DRAW_FILLING,颜色对是 clrGreen 和 clrRed,标签写 Profit;ZeroLine,意味着两条缓冲线之间的区域会被填充,零轴就是盈亏分界。 代码里还引了 ArrayObj.mqh,并在 CArrayObj::Search 里做有序数组定位:若数组空、指针无效或排序模式为 -1 直接返回 -1;否则走 QuickSearch,再比 Compare 是否等于 0,相等才返回位置,否则视作未找到。这套查找逻辑在你后面按开仓时间排序持仓对象时很关键,能避免线性扫描拖慢刷新。 实际加载后,若某根 K 线期间持仓净值为正,副图绿块向上撑;转负则红块向下吞。外汇与贵金属杠杆高,填充带只反映当下敞口盈亏,不预示走势,用它辅助平仓决策时仍需自担风险。

MQL5 / C++
class=class="str">"cmt">//|                                                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//--- plot Fill
class="macro">#class="kw">property indicator_label1  "Profit;ZeroLine"
class="macro">#class="kw">property indicator_type1   DRAW_FILLING
class="macro">#class="kw">property indicator_color1  clrGreen,clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     PositionInfoIndicator.mq5                     |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd.              |
class=class="str">"cmt">//|                             [MQL5官方文档]                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//--- plot Fill
class="macro">#class="kw">property indicator_label1  "Profit;ZeroLine"
class="macro">#class="kw">property indicator_type1   DRAW_FILLING
class="macro">#class="kw">property indicator_color1  clrGreen,clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <Arrays\ArrayObj.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Search of position of element in a sorted array                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CArrayObj::Search(class="kw">const CObject *element) class="kw">const
  {
   class="type">int pos;
class=class="str">"cmt">//--- check
   if(m_data_total==class="num">0 || !CheckPointer(element) || m_sort_mode==-class="num">1)
      class="kw">return(-class="num">1);
class=class="str">"cmt">//--- search
   pos=QuickSearch(element);
   if(m_data[pos].Compare(element,m_sort_mode)==class="num">0)
      class="kw">return(pos);
class=class="str">"cmt">//--- not found

◍ 成交与持仓的排序枚举怎么定义

在 MT5 里做持仓盈亏可视化指标时,先把底层数据对象的排序维度固化成枚举,后续数组排序才不会乱。下面这段来自一个独立窗口指标的工程代码,用两个 enum 把「成交」和「持仓」的可排字段全部列了出来。 DEAL_SORT_MODE 覆盖了 14 种成交排序键:从毫秒级执行时间、票号、持仓 ID、魔术码,到类型、开平方向、成交量、价格、佣金、库存费、盈亏、手续费、品种名。POS_SORT_MODE 则区分了开仓/平仓的毫秒时间与普通时间,以及开仓单号与平仓单号。 实际用处很直接:当你用 CArrayObj 装载历史成交或持仓,调用 Sort() 时传入对应枚举值,就能按盈利大小或平仓时间轴重排。外汇与贵金属杠杆高,重排后做权益曲线填充图,能更直观看出哪段行情的回撤概率偏大。 别把枚举当装饰。若你要接小布盯盘做批量回测,少写一个 SWAP 或 FEE 键,跨品种贵金属的隔夜成本就可能被漏算,回测净值会偏乐观。

MQL5 / C++
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- Deal object sorting modes
enum ENUM_DEAL_SORT_MODE
  {
   DEAL_SORT_MODE_TIME_MSC,    class=class="str">"cmt">// Deal execution time in milliseconds
   DEAL_SORT_MODE_TIME,        class=class="str">"cmt">// Deal execution time
   DEAL_SORT_MODE_TIKET,       class=class="str">"cmt">// Deal ticket
   DEAL_SORT_MODE_POS_ID,      class=class="str">"cmt">// Position ID
   DEAL_SORT_MODE_MAGIC,       class=class="str">"cmt">// Magic number for a deal
   DEAL_SORT_MODE_TYPE,        class=class="str">"cmt">// Deal type
   DEAL_SORT_MODE_ENTRY,       class=class="str">"cmt">// Deal entry - entry in, entry out, reverse
   DEAL_SORT_MODE_VOLUME,      class=class="str">"cmt">// Deal volume
   DEAL_SORT_MODE_PRICE,       class=class="str">"cmt">// Deal price
   DEAL_SORT_MODE_COMISSION,   class=class="str">"cmt">// Deal commission
   DEAL_SORT_MODE_SWAP,        class=class="str">"cmt">// Accumulated swap when closing
   DEAL_SORT_MODE_PROFIT,      class=class="str">"cmt">// Deal financial result
   DEAL_SORT_MODE_FEE,         class=class="str">"cmt">// Deal fee
   DEAL_SORT_MODE_SYMBOL,      class=class="str">"cmt">// Name of the symbol for which the deal is executed
   };
class=class="str">"cmt">//--- Position object sorting modes
enum ENUM_POS_SORT_MODE
  {
   POS_SORT_MODE_TIME_IN_MSC, class=class="str">"cmt">// Open time in milliseconds
   POS_SORT_MODE_TIME_OUT_MSC,class=class="str">"cmt">// Close time in milliseconds
   POS_SORT_MODE_TIME_IN,     class=class="str">"cmt">// Open time
   POS_SORT_MODE_TIME_OUT,    class=class="str">"cmt">// Close time
   POS_SORT_MODE_DEAL_IN,     class=class="str">"cmt">// Open deal ticket
   POS_SORT_MODE_DEAL_OUT,    class=class="str">"cmt">// Close deal ticket
   };

「持仓排序枚举的字段含义」

在 MT5 的持仓管理逻辑里,排序模式靠一组枚举常量来指定,直接决定后续遍历持仓时按什么键排。 下面这段枚举把可选的排序维度列得很清楚:POS_SORT_MODE_ID 按持仓唯一 ID 排;POS_SORT_MODE_MAGIC 按 EA 的 magic 号排;POS_SORT_MODE_PRICE_IN 按开仓价排;POS_SORT_MODE_PRICE_OUT 按平仓价排;POS_SORT_MODE_VOLUME 按手数排;POS_SORT_MODE_SYMBOL 按交易品种排。 实盘里若同时跑多个黄金 EA,用 MAGIC 排序能快速隔离各自持仓,避免误平别人的单;纯手工多品种盯盘则用 SYMBOL 排序更顺手。外汇与贵金属杠杆高,批量操作前务必在策略测试器用小资金验证排序结果。

MQL5 / C++
  POS_SORT_MODE_ID,           class=class="str">"cmt">// Position ID
  POS_SORT_MODE_MAGIC,        class=class="str">"cmt">// Position magic
  POS_SORT_MODE_PRICE_IN,     class=class="str">"cmt">// Open price
  POS_SORT_MODE_PRICE_OUT,    class=class="str">"cmt">// Close price
  POS_SORT_MODE_VOLUME,       class=class="str">"cmt">// Position volume
  POS_SORT_MODE_SYMBOL,       class=class="str">"cmt">// Position symbol
};

交易类如何把每笔成交归档成仓位

交易类的结构很直白:私有段只放成交属性变量,公有段给每个属性配了 get/set 方法。Compare() 是个虚函数,按调用时传入的比较模式挑属性比对——当前对象该属性值更大返回 1,更小返回 -1,相等返回 0,方便后续按某一维度排序。 构造函数里默认这笔成交已被选中,直接把属性写进私有变量。这样每从终端历史里捞一笔成交,就 new 一个对象,里面已经装好了 ticket、magic、position_id、time_msc 等 13 个字段。 关键动作在 position_id:从对象里取出仓位 ID,再去建历史仓位类的对象,把同一 ID 下的所有成交挂到它的交易列表里。跑完之后,一个历史仓位对象就聚合了它全生命周期的成交,提取仓位历史只需遍历这个列表。 下面这段是成交类的骨架声明,私有变量和注释一一对应,复制进 MT5 就能编译看结构。外汇与贵金属历史回测含跳空与滑点风险,实盘统计结果可能偏离历史样本。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deal class                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CDeal : class="kw">public CObject
  {
class="kw">private:
   class="type">long              m_ticket;              class=class="str">"cmt">// Deal ticket
   class="type">long              m_magic;               class=class="str">"cmt">// Magic number for a deal
   class="type">long              m_position_id;         class=class="str">"cmt">// Position ID
   class="type">long              m_time_msc;            class=class="str">"cmt">// Deal execution time in milliseconds
   class="type">class="kw">datetime          m_time;                class=class="str">"cmt">// Deal execution time
   ENUM_DEAL_TYPE   m_type;                class=class="str">"cmt">// Deal type
   ENUM_DEAL_ENTRY  m_entry;               class=class="str">"cmt">// Deal entry - entry in, entry out, reverse
   class="type">class="kw">double            m_volume;             class=class="str">"cmt">// Deal volume
   class="type">class="kw">double            m_price;              class=class="str">"cmt">// Deal price
   class="type">class="kw">double            m_comission;          class=class="str">"cmt">// Deal commission
   class="type">class="kw">double            m_swap;               class=class="str">"cmt">// Accumulated swap when closing
   class="type">class="kw">double            m_profit;             class=class="str">"cmt">// Deal financial result
   class="type">class="kw">double            m_fee;                class=class="str">"cmt">// Deal fee
   class="type">class="kw">string            m_symbol;             class=class="str">"cmt">// Name of the symbol for which the deal is executed
class=class="str">"cmt">//--- Return the deal direction description
   class="type">class="kw">string            EntryDescription(class="type">void) class="kw">const
     {

常见问题

用自定义指标读取账户历史成交,按品种和票号归档成仓位,再用箭头或色块标在对应 K 线位置即可。
账户历史只存成交与结算数据,没有现成函数直接给持仓过程盈亏,需要自己按开平时间切片计算后画副图。
可以,小布盯盘的 AIGC 已内置历史仓位诊断,打开对应品种页就能直接看归档好的仓位与盈亏分布。
票号是同一笔仓位多次成交共用的标识,用来把加仓减仓归到同一个仓位里,字段通常为 deal 的 order 关联值。
按成交类型枚举判断:入場类归为开仓,出場类归为平仓,再按品种与票号合并成完整仓位对象。