MQL5 EA 编码入门:从零搭建你自己的订单类(基础篇)
(1/3)· 很多新手直接套用标准库,却看不懂订单背后发生了什么,本文带你拆开黑匣子
从零搭一个会自己跑的 EA 骨架
在 MT5 里写 EA,第一步不是急着下单,而是把程序生命周期的三个钩子函数先立住:OnInit 初始化、OnTick 每笔报价触发、OnDeinit 退出清理。这三个函数是 MT5 终端调度 EA 的硬性入口,缺一个都可能让策略在实盘里行为异常。 MQL5 的 EA 模板默认就带这三个空函数,但很多人直接往 OnTick 里堆逻辑,忽略了 OnInit 里做句柄缓存——结果每跳都重新申请 iMA 句柄,CPU 占用和延迟都会悄悄恶化。 下面这段是最小可编译骨架,保留了核心结构、未加任何交易动作,你直接贴进 MetaEditor 就能编译通过并加载到图表观察日志输出。外汇与贵金属品种波动剧烈,实盘前务必先在策略测试器用历史数据跑通逻辑,高风险品种切勿直接挂真仓。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property strict class="type">int OnInit() { Print("EA initialized"); class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(class="kw">const class="type">int reason) { Print("EA deinitialized, reason=",reason); } class="type">void OnTick() { class="kw">static class="type">int ticks=class="num">0; ticks++; if(ticks%class="num">1000==class="num">0) Print("Processed ticks: ",ticks); } class=class="str">"cmt">//+------------------------------------------------------------------+
「从结构走向订单系统代码」
前一篇讲了自动 EA 的概念与骨架,但没碰一行代码。那一篇的定位是给零基础读者补环境认知:先搞清楚 MT5 里 EA 跑在什么上下文里,再谈写逻辑。 本篇直接进代码层。起点不是指标也不是风控,而是订单系统——任何 EA 在 MT5 里下单、撤单、查持仓,全得先把这个底层打通。外汇与贵金属保证金交易杠杆高,实盘前务必在策略测试器用历史数据验证逻辑。 下面这段是订单系统的最小骨架,后面所有交易动作都挂在它上面。
◍ 自己造一个发单函数库
MT5 标准库把订单系统封装得很顺手,但这次不打算让你继续当黑盒用户。我们要从零写一个自己的发单库,只保留搭建稳健订单系统真正需要的那部分功能,不堆砌标准库里那些花哨资源。 对刚碰 MQL5 的人先说在前头:后面会用到特定结构体,得自己啃才能跟住逻辑。我会把思路和概念拆到最直白,但如果你不花力气,最后只能看看我们造出来的东西,没法自己改写复用。 这个自建库的目标很具体——能在 MT5 里编译后直接发单、挂单、维护持仓,而不是调 CJA 那种现成接口。外汇和贵金属杠杆高,任何自建发单逻辑上线前都先用策略测试器跑历史tick验证。
从零搭一个订单管理类骨架
在 MT5 的 MetaEditor 里,右键 Include 文件夹选新建文件,就能得到一个空的 .mqh 头文件。很多人会直接用向导里的“新建类”模板,但那样生成的类自带预定义格式,不利于理解底层结构;手动从头写更能看清访问控制是怎么运作的。 类本质上是一种自定义数据类型,比 int、double、bool 这些基元类型能封装更多行为。程序调用方不需要知道类内部怎么实现,只要会用公开接口就行,这正是隔离风险、提升健壮性的关键。 类里用 private 和 public 保留字切分作用域:private 段里的变量和函数只能在类内部访问,外部改不了;public 段则全局可读写。如果完全不写这两个保留字,所有成员默认公开,等于废掉了类的封装保护。 建好 C_Orders 这个空类后,EA 主文件用 #include <Generic Auto Trader\C_Orders.mqh> 把它挂进来,编译器在预处理阶段就会把头文件内容合并进工程一起编译。下面这段代码就是刚建好的最小骨架和挂载方式,灰色注释行都可删。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property link "" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| defines | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// class="macro">#define MacrosHello "Hello, world!" class=class="str">"cmt">// class="macro">#define MacrosYear class="num">2010 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DLL imports | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// class="macro">#class="kw">import "user32.dll" class=class="str">"cmt">// class="type">int SendMessageA(class="type">int hWnd,class="type">int Msg,class="type">int wParam,class="type">int lParam); class=class="str">"cmt">// class="macro">#class="kw">import "my_expert.dll" class=class="str">"cmt">// class="type">int ExpertRecalculate(class="type">int wParam,class="type">int lParam); class=class="str">"cmt">// class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EX5 imports | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// class="macro">#class="kw">import "stdlib.ex5" class=class="str">"cmt">// class="type">class="kw">string ErrorDescription(class="type">int error_code); class=class="str">"cmt">// class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_Orders { class="kw">private : class="kw">public : }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Generic Auto Trader\C_Orders.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="kw">return INIT_SUCCEEDED; }
「空壳回调里藏着初始化顺序的坑」
上面这段 MQL5 骨架里,OnDeinit 和 OnTick 都是空实现,但 EA 加载到 MT5 图表时,这两段并非摆设。OnDeinit(const int reason) 在指标或 EA 被移除、图表周期切换、或终端重载时触发,reason 会带回退出的整数码,比如 REASON_REMOVE=1、REASON_RECOMPILE=2,不接参数就丢了现场上下文。 OnTick() 是每来一个报价就跑一次,空函数意味着你还没挂任何逻辑,但 MT5 仍会按 tick 频率调用它,在高频品种上会吃掉极少量 CPU。想验证就新建一个 EA,把这俩函数原样贴进去编译,用 Experts 日志看加载和卸载时的调用时机。 外汇与贵金属杠杆高、滑点跳空频繁,空壳 EA 虽不交易,但若后续往 OnTick 里塞重计算,可能因 tick 密度暴增拖垮终端,建议先在策略测试器用 2023 年 XAUUSD 的 M1 数据跑压力观察。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { } class=class="str">"cmt">//+------------------------------------------------------------------+