跨平台智能交易程序: 订单(基础篇)
MT5 里的订单到底长什么样
在 MetaTrader 5 中,订单(order)和成交(deal)是两个被刻意拆开的概念。很多从 MT4 迁过来的老交易员习惯把“下单”当成一锤子买卖,但在 MT5 的底层逻辑里,一个订单从挂出到成交可能经历多个状态,理解这一点才能写对 EA。 MT5 的订单类型包含市价单、限价单、止损单以及对应的止盈变体,系统用枚举值 ORDER_TYPE 来区分。一个订单被提交后,可能处于已受理、已部分成交、已成交或已取消等状态,这些状态由 ORDER_STATE 描述,EA 里查单不能只盯“有没有成交”,还要看中间态。 根据平台公开文档的整理记录,这类订单基础机制的说明早在 2016-10-07 就由社区作者 Enrico Lambino 发布,至今累计浏览约 5472 次。数字不说明策略优劣,只说明这是绕不开的底层知识——外汇与贵金属杠杆高、滑点随机,动手改 EA 前先把订单状态机在策略测试器里跑通。
◍ 这套文档到底覆盖了哪些模块
这一节把后续要展开的技术骨架先列了出来,方便你对照 MT5 终端里的实际对象去定位。从概论和约定起手,分别覆盖 MT4 的订单模型、MT5 的净持模式与对冲模式,再往下是单号、状态、交易量、订单容器这些底层字段。 例程和扩展两节会给出可直接粘贴的 MQL5 代码,结论部分收一下不同账户类型下的编码差异。你打开 MT5 的导航器—指标样例,就能看到这些对象在真实环境里的命名,和下面这份目录一一对得上。 目录里最容易被忽略的是「MetaTrader 5, 净持」和「对冲」的分列:同一套交易函数,在两种账户下返回值结构不同,写 EA 前先确认账户类型,能省掉后期大半的调试时间。
「用类对象抹平 MT4 与 MT5 的交易约定差」
MT4 与 MT5 在交易请求的处理约定上并不一致,直接写跨平台 EA 时,同一段下单逻辑往往要分两套分支维护。 一个可行的做法是把“由服务器处理的交易”抽象成类对象,让上层策略只调用统一接口,底层再去适配平台版本和使用模式。 这样跨平台智能交易程序可以在不关心当前跑的是 MT4 还是 MT5 的前提下工作,后续换平台或改下单模式时,改动集中在类内部而非散落各处的条件判断。
跨平台订单模型的三种约定差异
MT4、MT5净持、MT5对冲三种环境对订单和持仓的定义完全不同,写EA前必须先认清楚自己跑在哪套约定上。MT4里一笔市价单成功发送后拿到ticket,平单改单都用同一个号;但部分平仓时用OrderClose指定少于总手数的量,剩余手数会被服务器拆成同方向新订单,新ticket只能回扫账户活跃订单列表拿到——OrderClose只返布尔值,不像OrderSend成功直接给有效单号。 MT5净持模式默认把所有成交合并成单一持仓,品种方向随后续订单翻转:例如已有0.1手多头,砸进1.0手卖单后持仓直接变成0.9手空头。它不允许对冲,订单执行后客户端不可改属性,交易请求和成交严格分离,反馈只有「已处理/未处理」两态,且支持部分成交——这是MT4没有的,MT4订单只能全成或全否。 MT5对冲模式则接近MT4,允许同品种多持仓并存,挂单触发或市价成交才生成独立仓位。要让一套EA跨这三种约定都能干活,可行思路是每次成交后把订单细节存进自建类对象,让策略自己记住交易,而不是依赖平台持仓结构。 下面这段COrderBase基类声明就是干这事的基础容器,把ticket、类型、手数、magic等字段缓存下来,EA运行期内就能脱离平台差异自行追踪。注意类的实例只活在EA运行期,终端或EA重启就丢,除非你额外写中间文件做持久化。
class COrderBase : class="kw">public CObject { class="kw">protected: class="type">bool m_closed; class="type">bool m_suspend; class="type">long m_order_flags; class="type">int m_magic; class="type">class="kw">double m_price; class="type">class="kw">ulong m_ticket; ENUM_ORDER_TYPE m_type; class="type">class="kw">double m_volume; class="type">class="kw">double m_volume_initial; class="type">class="kw">string m_symbol; class="kw">public: COrderBase(class="type">void); ~COrderBase(class="type">void); class=class="str">"cmt">//--- 取值与赋值 class="type">void IsClosed(class="kw">const class="type">bool); class="type">bool IsClosed(class="type">void) class="kw">const; class="type">void IsSuspended(class="kw">const class="type">bool); class="type">bool IsSuspended(class="type">void) class="kw">const; class="type">void Magic(class="kw">const class="type">int); class="type">int Magic(class="type">void) class="kw">const; class="type">void Price(class="kw">const class="type">class="kw">double); class="type">class="kw">double Price(class="type">void) class="kw">const; class="type">void OrderType(class="kw">const ENUM_ORDER_TYPE); ENUM_ORDER_TYPE OrderType(class="type">void) class="kw">const;
◍ 订单对象的存取接口长这样
在 MT5 的自定义订单类里,Symbol、Ticket、Volume 这类成员都做了双向重载:带参版本用于写入,无参 const 版本用于只读取出。比如 Volume() 无参返回 double,配合 Volume(const double) 就能在回测里先塞入 0.1 手再随时读回校验。 IsOrderTypeLong 与 IsOrderTypeShort 是静态方法,直接传 ENUM_ORDER_TYPE 即可判断方向,不用先实例化对象。订单类型转字符串则留给虚函数 OrderTypeToString,派生类可各自重写。 把这段声明贴进 MQ5 编辑器,建个 COrder 子类,能立刻跑通「挂单—改手数—判多空」的最小闭环。外汇与贵金属杠杆高,这类底层封装若手数写错,实盘可能瞬间放大风险敞口。
class="type">void Symbol(class="kw">const class="type">class="kw">string); class="type">class="kw">string Symbol(class="type">void) class="kw">const; class="type">void Ticket(class="kw">const class="type">class="kw">ulong); class="type">class="kw">ulong Ticket(class="type">void) class="kw">const; class="type">void Volume(class="kw">const class="type">class="kw">double); class="type">class="kw">double Volume(class="type">void) class="kw">const; class="type">void VolumeInitial(class="kw">const class="type">class="kw">double); class="type">class="kw">double VolumeInitial(class="type">void) class="kw">const; class=class="str">"cmt">//--- 输出 class="kw">virtual class="type">class="kw">string OrderTypeToString(class="type">void) class="kw">const; class=class="str">"cmt">//--- 静态方法 class="kw">static class="type">bool IsOrderTypeLong(class="kw">const ENUM_ORDER_TYPE); class="kw">static class="type">bool IsOrderTypeShort(class="kw">const ENUM_ORDER_TYPE); };