MT5 订单、持仓、成交:三层模型与缓存陷阱
理解 Order / Position / Deal,邃开 MQL5 交易函数最常见的性能坑
一、三对象再梳理
MQL5 把一笔交易拆成三个清晰概念:Order 是你发出的指令(挂着的或已成交的),Deal 是实际发生的成交记录(钱货交割),Position 是当前持有的净仓位。三者生命周期不同:Order 可能一直挂着、Deal 一旦发生就 immutable、Position 随开平仓变化。理清它们,是所有交易查询的起点。
新手最常见的混乱是「我想看当前赚多少」却去查 Order。答案在 Position(净仓位)里,不在 Order。把三对象映射关系记牢,取数函数就不会用错。
二、Order:订单
订单是「意图」:你下的市价单(立即尝试成交)、挂单(等条件成交)、或止损单(触发后变市价)。未成交的挂单一直以 Order 形态存在;成交后它从「当前订单」移入历史,对应的 Deal 生成。用 OrdersTotal / OrderSelect 查当前与历史订单。
ulong tkt = OrderSend(...);
if(OrderSelect(tkt))
Print("state=", OrderGetInteger(ORDER_STATE),
" type=", OrderGetInteger(ORDER_TYPE));
三、Deal:成交记录
Deal 是「事实」:每一笔实际撮合都会产生一条 Deal,记录成交价、成交量、手续费、方向。它是不可变的——历史就是历史。统计真实盈亏、滑点、手续费,都要从 Deal 取。HistorySelect 选定时间窗后,用 HistoryDealsTotal 遍历。
四、Position:净持仓
MT5 是净额制:同一品种只一个 Position,加仓是改它的 volume 而非新建。Position 上有开仓价、当前价、浮动盈亏、魔术码(magic)。查当前持仓用 PositionsTotal + PositionSelect(_Symbol),再用 PositionGetDouble 取明细。想「平掉某品种全部」,操作的也是这个 Position。
if(PositionSelect(_Symbol))
{
double vol = PositionGetDouble(POSITION_VOLUME);
double profit = PositionGetDouble(POSITION_PROFIT);
Print("持仓 ", vol, " 浮盈 ", profit);
}
五、查询历史成交
要复盘某段时间的真实交易,用 HistorySelect(from, to) 加载历史,再遍历 HistoryDealsTotal 取每笔 Deal 的详情。注意时间窗要足够宽,跨日交易别漏;且 HistorySelect 是「加载」动作,没调用就取不到历史数据,这是常见空结果的原因。
六、三对象的实战配合
一个完整回合:发 Order → 成交产生 Deal → 汇总成 Position;平仓时发新 Order → 新 Deal 冲减 Position → Position 归零。EA 的状态机通常盯 Position(我在不在场)与 Deal(刚发生了什么),Order 只用于「我刚才发了什么指令」。三者各看一面,拼出完整图景。
七、常见坑
- 把 Order 当持仓查当前盈亏(应看 Position)
- 忘了 HistorySelect 就遍历历史,拿到空
- 想平多个「订单」却忽略了净额制下只有一个 Position
- 用错 magic 过滤,把别人的单算到自己头上
- 跨日交易时间窗太窄漏掉 Deal
八、用 Deal 算真实盈亏与手续费
Position 给你当前在场的浮盈,而真实的历史盈亏藏在 Deal 里。每笔 Deal 记录成交价、成交量、手续费(Commission)、掉期(Swap)。复盘一段时期的净收益,必须遍历 HistorySelect 后的 Deal,而不是只看 Position——Position 只反映此刻,Deal 才反映全过程。
实务中常见需求:算某笔持仓的实际成本(含手续费)以决定平仓阈值。这时要从 Deal 里把该品种的 commission 与 swap 累加,再和 Position 的浮盈比较,得到扣除费用后的真实盈利。忽视手续费,回测好看实盘亏,是新手最常踩的坑之一。
HistorySelect(TimeCurrent()-30*86400, TimeCurrent());
double net = 0;
for(int i=0; i<HistoryDealsTotal(); i++)
{
ulong t = HistoryDealGetTicket(i);
if(HistoryDealGetString(t, DEAL_SYMBOL) != _Symbol) continue;
net += HistoryDealGetDouble(t, DEAL_PROFIT)
+ HistoryDealGetDouble(t, DEAL_COMMISSION)
+ HistoryDealGetDouble(t, DEAL_SWAP);
}
九、净额制下的加仓与减仓语义
MT5 净额制意味着同一品种永远只有一个 Position。加仓不是新建仓,而是把现有 Position 的 volume 改大、开仓价变成加权平均;平仓一部分则是 volume 减小、剩余仓位的开仓价不变。理解这一点,你就不会误以为平掉一半能锁定那一半的成本——成本是按剩余仓位重新摊的。
这点和 MT4 的锁仓制(同品种可多空各一单)截然不同。净制的优势是逻辑干净、查询简单(PositionSelect 一次搞定),代价是没法在同一品种同时持多空。写 EA 时若从 MT4 迁移,务必重写持仓管理分支,否则会出现以为平了其实在加仓的严重错误。