图表上的历史仓位及其盈利/亏损图指标·进阶篇
(2/3)· 净额与对冲账户之外,历史平仓数据为何没有原生函数可查,本篇给出类设计解法
「成交方向与类型的可读化映射」
在 MT5 的 deal 对象封装里,把枚举值翻译成人话是日志与面板可视化的第一步。下面这段把建仓方向(entry)和成交类型(type)转成字符串,方便直接在 Print 或自定义面板里看。 entry 字段只有三种有效状态:DEAL_ENTRY_IN 是开仓进场,DEAL_ENTRY_OUT 是平仓出场,DEAL_ENTRY_INOUT 是反向反转(Reverce)。其余情况被归为「Close a position by an opposite one」,即被反向单强平。 type 的 switch 分支覆盖了 MT5 全部成交类别:从 BUY/SELL 这种真实交易,到 BALANCE、CREDIT、BONUS 等账户事件,再到 CHARGE、CORRECTION、COMMISSION 系列费用项。回测时若只看 BUY/SELL 过滤,会漏掉 BALANCE 变动导致的权益跳变——这是多数自建权益曲线和实盘对不上的隐性原因。 开 MT5 按 F4 建个结构体把这两段抄进去,用 Print(deal.TypeDescription()) 跑历史成交,能立刻核对你的成交分类是否和券商后台一致。外汇与贵金属杠杆高,成交类型里的 CHARGE/COMMISSION 若没算准,净值回撤可能被低估。
class="kw">return(this.m_entry==DEAL_ENTRY_IN ? "Entry In" : this.m_entry==DEAL_ENTRY_OUT ? "Entry Out" : this.m_entry==DEAL_ENTRY_INOUT ? "Reverce" : "Close a position by an opposite one"); class=class="str">"cmt">//--- Return the deal type description class="type">class="kw">string TypeDescription(class="type">void) class="kw">const { class="kw">switch(this.m_type) { case DEAL_TYPE_BUY : class="kw">return "Buy"; case DEAL_TYPE_SELL : class="kw">return "Sell"; case DEAL_TYPE_BALANCE : class="kw">return "Balance"; case DEAL_TYPE_CREDIT : class="kw">return "Credit"; case DEAL_TYPE_CHARGE : class="kw">return "Additional charge"; case DEAL_TYPE_CORRECTION : class="kw">return "Correction"; case DEAL_TYPE_BONUS : class="kw">return "Bonus"; case DEAL_TYPE_COMMISSION : class="kw">return "Additional commission"; case DEAL_TYPE_COMMISSION_DAILY : class="kw">return "Daily commission"; case DEAL_TYPE_COMMISSION_MONTHLY : class="kw">return "Monthly commission";
成交枚举与毫秒级时间的代码落地
在 MT5 的成交封装类里,把枚举值翻译成可读字符串是排查代理佣金、利息和分红的第一步。下面这段 switch 覆盖了日常不会被注意到的非交易类成交:日结/月结代理佣金、账户利息、买卖撤单、股息及含税股息、税务扣减,default 分支则把未识别类型连同原始 m_type 数值抛出来,方便你发现券商自定义的成交种类。 case DEAL_TYPE_COMMISSION_AGENT_DAILY : return "Daily agent commission"; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: return "Monthly agent commission"; case DEAL_TYPE_INTEREST : return "Interest rate"; case DEAL_TYPE_BUY_CANCELED : return "Canceled buy deal"; case DEAL_TYPE_SELL_CANCELED : return "Canceled sell deal"; case DEAL_DIVIDEND : return "Dividend operations"; case DEAL_DIVIDEND_FRANKED : return "Franked (non-taxable) dividend operations"; case DEAL_TAX : return "Tax charges"; default : return "Unknown: "+(string)this.m_type; 行情回放和剥头皮策略对时间戳精度敏感,自带 TimeToString 只到秒。TimeMSCtoString 用 time_msc/1000 取整拿秒级时间,time_msc%1000 取余补三位毫秒,拼出来就是「2024.05.13 09:30:01.247」这种格式,复盘滑点时有用。 Ticket() 直接返回 m_ticket,是后续用 HistoryDealGetTicket 做逐笔核对的主键。外汇和贵金属杠杆高,这类非方向性成交(佣金、利息、税)会悄悄吃掉净值,建议在自己的成交监听 EA 里把上面枚举全打出来再统计。
case DEAL_TYPE_COMMISSION_AGENT_DAILY : class="kw">return "Daily agent commission"; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: class="kw">return "Monthly agent commission"; case DEAL_TYPE_INTEREST : class="kw">return "Interest rate"; case DEAL_TYPE_BUY_CANCELED : class="kw">return "Canceled buy deal"; case DEAL_TYPE_SELL_CANCELED : class="kw">return "Canceled sell deal"; case DEAL_DIVIDEND : class="kw">return "Dividend operations"; case DEAL_DIVIDEND_FRANKED : class="kw">return "Franked(non-taxable) dividend operations"; case DEAL_TAX : class="kw">return "Tax charges"; class="kw">default : class="kw">return "Unknown: "+(class="type">class="kw">string)this.m_type; class=class="str">"cmt">//--- Return time with milliseconds class="type">class="kw">string TimeMSCtoString(class="kw">const class="type">long time_msc,class="type">int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) { class="kw">return ::TimeToString(time_msc/class="num">1000,flags)+"."+::IntegerToString(time_msc%class="num">1000,class="num">3,&class="macro">#x27;class="num">0&class="macro">#x27;); } class="kw">public: class=class="str">"cmt">//--- Methods for returning deal properties class="type">long Ticket(class="type">void) class="kw">const { class="kw">return this.m_ticket; } class=class="str">"cmt">// Deal ticket
◍ 成交记录类的只读访问器
在 MT5 的自定义交易类里,成交(deal)对象的元数据通常通过一组 const 成员函数暴露,调用方只能取不能改,避免历史成交被意外覆盖。 下面这段声明了 13 个只读接口:Magic 返回订单魔数,PositionID 返回关联持仓 ID,TimeMsc 与 Time 分别给出毫秒级和秒级成交时间,TypeDeal、Entry 描述成交类型与开平/反转方向。 Volume、Price 取成交量与成交价;Comission(原文拼写)、Swap、Profit、Fee 对应手续费、平仓时累计库存费、盈亏与额外费用。外汇与贵金属杠杆高,库存费在隔夜持仓上可能显著侵蚀 Profit,回测时建议把 Swap 单独打印出来核对。 把这些访问器直接贴进你的 CTrade 派生类,就能在 EA 里用 deal.Profit() 而非直接碰私有字段,降低出错概率。
class="type">long Magic(class="type">void) class="kw">const { class="kw">return this.m_magic; } class=class="str">"cmt">// Magic number for a deal class="type">long PositionID(class="type">void) class="kw">const { class="kw">return this.m_position_id; } class=class="str">"cmt">// Position ID class="type">long TimeMsc(class="type">void) class="kw">const { class="kw">return this.m_time_msc; } class=class="str">"cmt">// Deal execution time in milliseconds class="type">class="kw">datetime Time(class="type">void) class="kw">const { class="kw">return this.m_time; } class=class="str">"cmt">// Deal execution time ENUM_DEAL_TYPE TypeDeal(class="type">void) class="kw">const { class="kw">return this.m_type; } class=class="str">"cmt">// Deal type ENUM_DEAL_ENTRY Entry(class="type">void) class="kw">const { class="kw">return this.m_entry; } class=class="str">"cmt">// Deal entry - entry in, entry out, reverse class="type">class="kw">double Volume(class="type">void) class="kw">const { class="kw">return this.m_volume; } class=class="str">"cmt">// Deal volume class="type">class="kw">double Price(class="type">void) class="kw">const { class="kw">return this.m_price; } class=class="str">"cmt">// Deal price class="type">class="kw">double Comission(class="type">void) class="kw">const { class="kw">return this.m_comission; } class=class="str">"cmt">// Deal commission class="type">class="kw">double Swap(class="type">void) class="kw">const { class="kw">return this.m_swap; } class=class="str">"cmt">// Accumulated swap when closing class="type">class="kw">double Profit(class="type">void) class="kw">const { class="kw">return this.m_profit; } class=class="str">"cmt">// Deal financial result class="type">class="kw">double Fee(class="type">void) class="kw">const { class="kw">return this.m_fee; } class=class="str">"cmt">// Deal fee
「成交对象的字段读写接口」
在 MT5 的成交封装类里,Symbol() 以 const 方法返回当前成交所属品种名,内部直接取 this.m_symbol,这意味着一笔 deal 对象绑定后就不再换品种。 下方这批 Set 方法负责把历史成交中心的原始字段写进实例:SetTicket 存成交单号,SetMagic 存魔术码,SetPositionID 关联持仓 ID,SetTimeMsc / SetTime 分别落毫秒与秒级执行时间。 类型与方向由 SetType(ENUM_DEAL_TYPE) 和 SetEntry(ENUM_DEAL_ENTRY) 接管,后者区分进场、出场与反转三种动作。SetVolume、SetPrice 管量价,SetComission(原文拼写如此)、SetSwap、SetProfit 覆盖费用与盈亏——注意 Commission 拼错不影响编译,但复制时最好顺手修正。 开 MT5 新建 EA 时,若用 CTrade 或历史回放自己填 deal 结构,这套 Setter 可直接套用;外汇与贵金属杠杆高,回测里漏填 Swap 会令盈亏偏离实盘。
class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.m_symbol; } class=class="str">"cmt">// Name of the symbol, for which the deal is executed class=class="str">"cmt">//--- Methods for setting deal properties class="type">void SetTicket(class="kw">const class="type">long ticket) { this.m_ticket=ticket; } class=class="str">"cmt">// Deal ticket class="type">void SetMagic(class="kw">const class="type">long magic) { this.m_magic=magic; } class=class="str">"cmt">// Magic number for a deal class="type">void SetPositionID(class="kw">const class="type">long id) { this.m_position_id=id; } class=class="str">"cmt">// Position ID class="type">void SetTimeMsc(class="kw">const class="type">long time_msc) { this.m_time_msc=time_msc; } class=class="str">"cmt">// Deal execution time in milliseconds class="type">void SetTime(class="kw">const class="type">class="kw">datetime time) { this.m_time=time; } class=class="str">"cmt">// Deal execution time class="type">void SetType(class="kw">const ENUM_DEAL_TYPE type) { this.m_type=type; } class=class="str">"cmt">// Deal type class="type">void SetEntry(class="kw">const ENUM_DEAL_ENTRY entry) { this.m_entry=entry; } class=class="str">"cmt">// Deal entry - entry in, entry out, reverse class="type">void SetVolume(class="kw">const class="type">class="kw">double volume) { this.m_volume=volume; } class=class="str">"cmt">// Deal volume class="type">void SetPrice(class="kw">const class="type">class="kw">double price) { this.m_price=price; } class=class="str">"cmt">// Deal price class="type">void SetComission(class="kw">const class="type">class="kw">double comission) { this.m_comission=comission; } class=class="str">"cmt">// Deal commission class="type">void SetSwap(class="kw">const class="type">class="kw">double swap) { this.m_swap=swap; } class=class="str">"cmt">// Accumulated swap when closing class="type">void SetProfit(class="kw">const class="type">class="kw">double profit) { this.m_profit=profit; } class=class="str">"cmt">// Deal financial result
成交对象的比较与排序接口
在自建的成交记录类里,比较逻辑直接决定了后续按哪种维度做排序。下面这段实现把时间、毫秒时间、订单号、魔法码、持仓 ID 都做成了可切换的对比键,调用方只需传不同 mode 就能拿到升序或降序结果。 SetFee 和 SetSymbol 是两个极轻量的 setter,分别写入单笔手续费和该成交对应的交易品种名。注意 m_fee 和 m_symbol 都是对象内部成员,外部不直接暴露,避免历史成交被误改。 Compare 方法返回 1 / -1 / 0 三态:大于返回 1,小于返回 -1,相等返回 0。例如用 DEAL_SORT_MODE_TIME 时,若 this 的时间戳大于传入节点,返回 1,否则按大小落 -1 或 0。 外汇与贵金属杠杆高、滑点随机,回测里用 TimeMsc 做毫秒级排序,比只用秒级 Time 更不容易把同秒多单排错顺序,实盘核对成交流时建议优先走这个 mode。
class="type">void SetFee(class="kw">const class="type">class="kw">double fee) { this.m_fee=fee; } class=class="str">"cmt">// Deal fee class="type">void SetSymbol(class="kw">const class="type">class="kw">string symbol) { this.m_symbol=symbol; } class=class="str">"cmt">// Name of the symbol, for which the deal is executed class=class="str">"cmt">//--- Method for comparing two objects class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CDeal *compared_obj=node; class="kw">switch(mode) { case DEAL_SORT_MODE_TIME : class="kw">return(this.Time()>compared_obj.Time() ? class="num">1 : this.Time()<compared_obj.Time() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_TIME_MSC : class="kw">return(this.TimeMsc()>compared_obj.TimeMsc() ? class="num">1 : this.TimeMsc()<compared_obj.TimeMsc() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_TIKET : class="kw">return(this.Ticket()>compared_obj.Ticket() ? class="num">1 : this.Ticket()<compared_obj.Ticket() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_MAGIC : class="kw">return(this.Magic()>compared_obj.Magic() ? class="num">1 : this.Magic()<compared_obj.Magic() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_POS_ID : class="kw">return(this.PositionID()>compared_obj.PositionID() ? class="num">1 : this.PositionID()<compared_obj.PositionID() ? -class="num">1 : class="num">0);
◍ 成交记录的多字段排序分支
在 MT5 的成交类封装里,Compare 方法常靠一个枚举切换排序维度。下面这段分支覆盖了类型、开仓方向、手数、价格、佣金、库存费、盈亏与手续费共 8 个字段,返回值固定为 1 / -1 / 0 三态。 返回 1 表示当前对象该字段大于比较对象,返回 -1 为小于,相等则回 0,和标准库容器的排序契约一致。实盘跑历史成交统计时,换成 DEAL_SORT_MODE_PROFIT 就能直接按盈亏降序排,省掉自己写比较器。 外汇与贵金属杠杆高,成交滑点会让 Price() 比较结果和实际填单有偏差,排序只作复盘参考,别当成入场信号。
case DEAL_SORT_MODE_TYPE : class="kw">return(this.TypeDeal()>compared_obj.TypeDeal() ? class="num">1 : this.TypeDeal()<compared_obj.TypeDeal() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_ENTRY : class="kw">return(this.Entry()>compared_obj.Entry() ? class="num">1 : this.Entry()<compared_obj.Entry() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_VOLUME : class="kw">return(this.Volume()>compared_obj.Volume() ? class="num">1 : this.Volume()<compared_obj.Volume() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_PRICE : class="kw">return(this.Price()>compared_obj.Price() ? class="num">1 : this.Price()<compared_obj.Price() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_COMISSION : class="kw">return(this.Comission()>compared_obj.Comission() ? class="num">1 : this.Comission()<compared_obj.Comission() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_SWAP : class="kw">return(this.Swap()>compared_obj.Swap() ? class="num">1 : this.Swap()<compared_obj.Swap() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_PROFIT : class="kw">return(this.Profit()>compared_obj.Profit() ? class="num">1 : this.Profit()<compared_obj.Profit() ? -class="num">1 : class="num">0); case DEAL_SORT_MODE_FEE : class="kw">return(this.Fee()>compared_obj.Fee() ? class="num">1 : this.Fee()<compared_obj.Fee() ? -class="num">1 : class="num">0);