您需要了解的有关MQL5程序结构的所有信息·进阶篇
📘

您需要了解的有关MQL5程序结构的所有信息·进阶篇

第 2/2 篇

「外部参数与全局变量的初始化位置」

MQL5 里用 input 修饰符声明的变量是外部参数,编译后出现在 EA 属性面板的「输入」页。用户在 MT5 里改的是值,修饰符本身在程序运行期锁死,不能由代码改写。每次加载程序,这些 input 都会在 OnInit() 被调用前完成重初始化。 看一段典型声明:MA_Period 默认 20、MA_Shift 默认 0、MA_Method 默认 MODE_SMA。后面三行加了注释,面板里就会显示「Moving Average Period」这类说明文字,方便实盘切换周期和偏移时少猜含义。 全局变量必须写在事件处理函数之外、和函数同级的区域。上面代码里的 Globalvar 就是例子。它的作用域覆盖整个程序,任意函数都能读能写,且只在加载时初始化一次——早于 OnInit 或 OnStart 的执行点。 外汇与贵金属杠杆高、滑点随机,外部参数乱填可能让策略在极端波动中频繁重算。开 MT5 把这段贴进 EA 头部,改 MA_Period 从 20 到 50,观察面板与初始化日志的差异最直接。

MQL5 / C++
class="kw">input class="type">int               MA_Period=class="num">20;
class="kw">input class="type">int               MA_Shift=class="num">0;
class="kw">input ENUM_MA_METHOD MA_Method=MODE_SMA;
class="kw">input class="type">int               MA_Period=class="num">20;        class=class="str">"cmt">//Moving Average Period 
class="kw">input class="type">int               MA_Shift=class="num">0;         class=class="str">"cmt">//Moving Average Shift
class="kw">input ENUM_MA_METHOD MA_Method=MODE_SMA;   class=class="str">"cmt">//Moving Average Type
class="type">int Globalvar;   class=class="str">"cmt">// Global variable before or outside the event handler and functions
class="type">int OnInit()
  {
  ...
  }

函数和类能塞在程序哪个位置

MQL5 里函数和类不强制写在固定区域。自定义类尤其灵活:可以直接写在主文件任意处,也能抽进 include 文件再用 #include 挂进来——这和预处理器那节说的指令是同一套机制。 它们可以排在事件处理程序之前或之后,也能落在 input 变量和全局变量下方。实际写 EA 时,把类定义丢在全局变量之后、OnTick 之前,是最不容易报重定义错误的摆法。 下面这段是 MQL5 里函数与类的最小骨架,注意返回类型、参数括号和类尾的分号都省不掉:

MQL5 / C++
returnedDataType functionName(param1, param2)
{
     bodyOfFunction
}
class Cobject
{
   class="type">int var1;       class=class="str">"cmt">// variable1
   class="type">class="kw">double var2;    class=class="str">"cmt">// variable1
   class="type">void method1(); class=class="str">"cmt">// Method or function1
};
逐行拆一下:第一行声明函数返回类型加函数名和形参表;大括号内是函数体,不写内容编译也会过但没用。class 开头定义 Cobject,里面 int var1 和 double var2 是成员变量,void method1() 是方法声明,类末尾的分号告诉编译器类定义结束。 想验证摆放自由度,开 MT5 新建脚本,把上面 class 块剪切到 #property 之下、OnStart 之上和之下各编译一次,两种位置都应零报错。外汇与贵金属杠杆高,这类结构实验先在策略测试器用模拟 tick 跑,别直接挂实盘。

MQL5 / C++
returnedDataType functionName(param1, param2)
{
     bodyOfFunction
}
class Cobject
{
   class="type">int var1;       class=class="str">"cmt">// variable1
   class="type">class="kw">double var2;    class=class="str">"cmt">// variable1
   class="type">void method1(); class=class="str">"cmt">// Method or function1
};

◍ MT5事件处理函数怎么挂到程序生命周期上

写EA、指标或脚本,核心不是算指标,而是把代码挂到对的事件节点上。行情每来一个tick,终端就触发OnTick;EA初始化和退出分别走OnInit与OnDeinit,指标同理。脚本没有持续运行需求,启动即跑OnStart,跑完即退。 不同程序类型能用的处理函数不一样:脚本只能用OnStart;指标用OnInit、OnDeinit、OnCalculate、OnChartEvent等;EA最全,从OnTick、OnTimer到OnTrade、OnBookEvent、OnTester系列都能接。OnCalculate有两种重载,一种吃单个price数组,一种吃OHLCV加spread的完整时间序列,写自定义指标时选错签名会直接编译不过。 回测和优化阶段也有专门钩子:OnTester在单轮历史测试结束时跑,OnTesterInit和OnTesterDeinit框住整次优化,OnTesterPass在收到新数据帧时触发。想做参数寻优后自动导出结果,这几个函数比手动记日志靠谱。 外汇和贵金属杠杆高、滑点跳空频繁,事件触发顺序不保证严格实时,OnTrade里的仓位判断要做容错,别假设成交回报一定先到。

MQL5 / C++
class="type">int  OnStart(class="type">void);
class="type">void  OnStart(class="type">void);
class="type">int  OnInit(class="type">void);
class="type">void  OnInit(class="type">void);
class="type">void  OnDeinit(
  class="kw">const class="type">int  reason          class=class="str">"cmt">// deinitialization reason code
  );
class="type">void  OnTick(class="type">void);
class="type">int  OnCalculate(
  class="kw">const class="type">int        rates_total,     class=class="str">"cmt">// price[] array size
  class="kw">const class="type">int        prev_calculated, class=class="str">"cmt">// number of handled bars at the previous call
  class="kw">const class="type">int        begin,           class=class="str">"cmt">// index number in the price[] array meaningful data starts from
  class="kw">const class="type">class="kw">double&    price[]          class=class="str">"cmt">// array of values for calculation
  );
class="type">int  OnCalculate(
  class="kw">const class="type">int        rates_total,     class=class="str">"cmt">// size of class="kw">input time series
  class="kw">const class="type">int        prev_calculated, class=class="str">"cmt">// number of handled bars at the previous call
  class="kw">const class="type">class="kw">datetime&  time,            class=class="str">"cmt">// Time array
  class="kw">const class="type">class="kw">double&    open,            class=class="str">"cmt">// Open array
  class="kw">const class="type">class="kw">double&    high,            class=class="str">"cmt">// High array
  class="kw">const class="type">class="kw">double&    low,             class=class="str">"cmt">// Low array
  class="kw">const class="type">class="kw">double&    close,           class=class="str">"cmt">// Close array
  class="kw">const class="type">long&      tick_volume,     class=class="str">"cmt">// Tick Volume array
  class="kw">const class="type">long&      volume,          class=class="str">"cmt">// Real Volume array
  class="kw">const class="type">int&       spread           class=class="str">"cmt">// Spread array
  );
class="type">void  OnTimer(class="type">void);
class="type">void  OnTrade(class="type">void);
class="type">void  OnTradeTransaction()
  class="kw">const MqlTradeTransaction&    trans,     class=class="str">"cmt">// trade transaction structure

「事件回调函数的参数结构」

MT5 的预定义回调把交易、深度簿、图表和回测四类事件拆成了固定签名,写 EA 时直接套用就不会漏参。 OrderSendAsync 的回调里,request 是 MqlTradeRequest 的常引用,装委托单的所有字段;result 是 MqlTradeResult 的常引用,回装柜台应答。两者都用 & 传引用,避免结构体拷贝。 OnBookEvent 只收一个 symbol 字符串常引用,市场深度变动时触发,用来定位是哪个品种簿变了。 OnChartEvent 给得最杂:id 是事件编号,lparam 和 dparam 分装长整型和双精度参数,sparam 兜底字符串,比如鼠标点击的坐标或对象名都从这儿取。 回测侧有 OnTester 返回 double 做优化目标,OnTesterInit / OnTesterDeinit 管初始化和反初始化,OnTesterPass 在每帧参数组合跑完时回调。注意原文里 OnTesterInit 出现了 int 和 void 两种返回,实际只能留一个,编译会报重定义。

MQL5 / C++
  class="kw">const class="type">MqlTradeRequest&         request,   class=class="str">"cmt">// request structure
  class="kw">const class="type">MqlTradeResult&          result     class=class="str">"cmt">// response structure
  );
class="type">void  OnBookEvent(
  class="kw">const class="type">class="kw">string&    symbol       class=class="str">"cmt">// symbol
  );
class="type">void  OnChartEvent(
  class="kw">const class="type">int     id,      class=class="str">"cmt">// event ID 
  class="kw">const class="type">long&   lparam,  class=class="str">"cmt">// class="type">long type event parameter
  class="kw">const class="type">class="kw">double& dparam,  class=class="str">"cmt">// class="type">class="kw">double type event parameter
  class="kw">const class="type">class="kw">string& sparam   class=class="str">"cmt">// class="type">class="kw">string type event parameter
  );
class="type">class="kw">double  OnTester(class="type">void);
class="type">int   OnTesterInit(class="type">void);
class="type">void  OnTesterInit(class="type">void);
class="type">void  OnTesterDeinit(class="type">void);
class="type">void  OnTesterPass(class="type">void);

从脚本骨架看 MQL5 结构取舍

写 MQL5 程序不必把整套结构都堆上去。#include 引外部文件、自定义类或函数,都是按需选用,脚本类程序往往连事件处理函数都只用 OnStart() 一个入口。 上面这段脚本只做一件事:让用户在 MT5 弹窗里填两个整型数,相加后把结果打到「专家」标签页。注意 #property script_show_inputs 这一行,少了它输入参数界面不会弹出,这是脚本交互的关键开关。 代码逐行拆解: #property copyright / link / version 是元数据声明,编译后显示在文件属性里;#property script_show_inputs 强制脚本运行前显示输入框。 input int userEntryNum1、userEntryNum2 是外部可填变量;int result 为全局变量暂存求和值。 void OnStart() 是脚本唯一事件入口,大括号内 result=两输入相加,Print("Result: ", result) 输出到终端。 换 EA 或指标时,事件处理器得跟着变:EA 常用 OnTick() 接新分时触发,指标多用 OnCalculate()。先认清楚每段结构挂在哪种程序类型下,才不会把脚本写法硬套进 EA 里。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  Script program example.mq5 |
class=class="str">"cmt">//|                                                                 Copyright class="num">2023, MetaQuotes Ltd.|
class=class="str">"cmt">//|                                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="kw">property preprocessor
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//inputs
class="kw">input class="type">int userEntryNum1;
class="kw">input class="type">int userEntryNum2;
class=class="str">"cmt">//global variable
class="type">int result;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//event handler
class="type">void OnStart()
  {
   result=userEntryNum1+userEntryNum2;
   Print("Result: ", result);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 把结构顺序刻进编译习惯

MQL5 程序的编译顺序不是装饰,而是硬约束。预处理器指令必须排在宏替换之前,宏替换先于程序属性和包含文件,导入函数块要在全局变量之前就位,否则 MT5 编辑器会直接报『unexpected token』。 事件处理函数有固定入口名:脚本走 OnStart,EA 跑 OnInit、OnDeinit、OnTick,指标用 OnCalculate,定时器触发 OnTimer,成交相关走 OnTrade 与 OnTradeTransaction,深度回测还有 OnTester 系列四个钩子。把这些名字写错一个字母,程序能编译但事件不会响应。 打开下载包里的 Script_program_example.mq5(约 1.01 KB)对照看,文件里从 #property 到 OnStart 的排列就是上述序列的极简实例。外汇与贵金属杠杆高、滑点突发行情多,实盘前务必在策略测试器用历史数据跑通事件链。 下次新建 EA 时,先按这个顺序搭空骨架再填逻辑,比边写边改少踩一半编译坑。结构顺了,后面接 CMF、肯特纳或 Aroon 系统都只是往函数里塞代码的事。

常见问题

外部参数用input或extern必须放在文件顶层,全局变量也建议在包含和宏之后、函数之前初始化,这样编译期就能确定内存布局,避免运行时顺序错乱。
可以,但编译器要求被调用的函数或类在使用前已声明;把定义集中放在事件处理函数之前最稳,类定义则建议放头文件或文件前部。
小布可以读取你的代码骨架,标出外部参数位置、事件函数挂载点和生命周期钩子缺失,并给出重排建议,省去手动比对文档。
参数由终端在触发时自动填充,如OnTradeTransaction带交易事件结构,你不能改签名,只能按官方结构体解析字段。
单文件便于快速验证和移植,但超过数百行后初始化顺序和类作用域容易乱;按事件生命周期切分文件更利于长期维护。