首页 / 交易学院 / 小布知识库 / MT5 订单、持仓、成交:三层模型与缓存陷阱
MT5 订单、持仓、成交:三层模型与缓存陷阱
📑

MT5 订单、持仓、成交:三层模型与缓存陷阱

理解 Order / Position / Deal,邃开 MQL5 交易函数最常见的性能坑

EA 开发实战 难度 · 进阶 2026-07-15 13 分钟阅读
#MT5#订单#持仓#成交#缓存#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 只用于「我刚才发了什么指令」。三者各看一面,拼出完整图景。

七、常见坑

八、用 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);
}
✦ 账面与到手
浮盈是账面的,Deal 净额是到手的。做绩效评估永远用 Deal 口径,否则会被未平仓的浮盈误导。

九、净额制下的加仓与减仓语义

MT5 净额制意味着同一品种永远只有一个 Position。加仓不是新建仓,而是把现有 Position 的 volume 改大、开仓价变成加权平均;平仓一部分则是 volume 减小、剩余仓位的开仓价不变。理解这一点,你就不会误以为平掉一半能锁定那一半的成本——成本是按剩余仓位重新摊的。

这点和 MT4 的锁仓制(同品种可多空各一单)截然不同。净制的优势是逻辑干净、查询简单(PositionSelect 一次搞定),代价是没法在同一品种同时持多空。写 EA 时若从 MT4 迁移,务必重写持仓管理分支,否则会出现以为平了其实在加仓的严重错误。

✦ 净制灵魂
一句话:同品种,一个仓,加减都是改 volume。把这句话刻进持仓管理代码,能避开一大类迁移 bug。

十、小结

✦ Order 是意图,Deal 是事实,Position 是状态
记住这句口诀:发单看 Order、算真实盈亏看 Deal、看当前在场看 Position。三对象各司其职,查询才不会张冠李戴。

常见问题

查 Position(净持仓),不是 Order。用 PositionsTotal + PositionSelect(_Symbol) 再取明细。MQL5 同品种只有一个 Position(净额制)。
多半忘了先 HistorySelect(from, to) 加载历史区间。没调用它,HistoryDealsTotal 取不到数据;时间窗也要够宽,别漏跨日交易。
Order 是你发出的指令(可能挂着未成交),Deal 是实际撮合后不可变的成交记录。统计真实盈亏/滑点必须从 Deal 取。