Program Running
程序运行
每个脚本和每个专家顾问都在独立的线程中运行。所有指标都在图形界面线程中工作。Tick处理和历史数据同步也在图形界面线程中进行。自定义指标在主界面线程中工作。如果自定义指标是通过iCustom()函数调用的,那么该指标将在调用它的程序的线程中工作。库(导入)函数也在调用程序线程中工作。
运行专家顾问时,确保它有一个真实的交易环境并且能够访问所需符号和期间的历史数据,并在终端和服务器之间同步数据。对于所有这些操作,终端的启动延迟不超过5秒,之后专家顾问将使用可用的数据开始运行。因此,如果没有连接到服务器,可能会导致专家顾问启动延迟。
下表简要总结了MQL4程序:
| 程序 | 运行状态 | 备注 |
|---|---|---|
| 脚本 | 独立的线程,脚本的线程数等于脚本数量 | 循环脚本不能中断其他程序的运行 |
| 专家顾问 | 独立的线程,专家顾问的线程数等于专家顾问数量 | 循环专家顾问不能中断其他程序的运行 |
| 指标 | 所有指标共享终端图形界面线程的资源 | 指标中的无限循环将停止终端的工作 |
程序一旦附加到图表,就会上传到客户端终端内存,同时全局变量也会初始化。如果某个类类型的全局变量有构造函数,则在全局变量初始化期间将调用该构造函数。
之后,程序等待客户端终端的事件。每个mql4程序至少应有一个事件处理程序,否则加载的程序将不会执行。事件处理程序有预定义的名称、参数和返回类型。
| 类型 | 函数名称 | 参数 | 应用 | 备注 |
|---|---|---|---|---|
| int | OnInit | 无 | 专家顾问、指标和脚本 | Init事件处理程序。允许使用void返回类型。 |
| void | OnDeinit | const int reason | 专家顾问、指标和脚本 | Deinit事件处理程序。 |
| void | OnStart | 无 | 脚本 | Start事件处理程序。 |
| int | OnCalculate | const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[] | 指标 | 所有价格的计算事件处理程序。 |
| void | OnTick | 无 | 专家顾问 | NewTick事件处理程序。在处理新Tick事件时,不会接收其他此类事件。 |
| void | OnTimer | 无 | 专家顾问和指标 | Timer事件处理程序。 |
| double | OnTester | 无 | 专家顾问 | Tester事件处理程序。 |
| void | OnChartEvent | const int id, const long &lparam, const double &dparam, const string &sparam | 专家顾问和指标 | ChartEvent事件处理程序。 |
客户端终端向相应的开放图表发送新事件。事件也可以由图表生成(图表事件)或mql4程序(自定义事件)。可以通过设置CHART_EVENT_OBJECT_CREATE和CHART_EVENT_OBJECT_DELETE图表属性来启用或禁用图表上图形对象的创建或删除事件。每个MQL4程序和每个图表都有自己的事件队列,所有新到达的事件都会添加到其中。
程序仅接收其运行的图表上的事件。所有事件按接收顺序依次处理。如果队列中已经有一个NewTick事件,或者该事件正在处理中,那么新的NewTick事件不会放入MQL4程序的队列中。同样,如果ChartEvent已经入队,或者该事件正在处理中,也不会加入新的此类事件。计时器事件的处理方式相同——如果Timer事件在队列中或正在处理中,那么新的计时器事件不会入队。
事件队列的大小有限但足够大,因此对于编写良好的程序来说,队列溢出是不太可能的。如果队列溢出,新事件将被丢弃而不入队。
不建议使用无限循环来处理事件。这条规则的例外情况只有处理单个Start事件的脚本。
库不处理任何事件。
指标和专家顾问中禁止使用的函数
指标、脚本和专家顾问是用MQL4编写的可执行程序。它们适用于不同类型的任务。因此,根据程序类型,对某些函数的使用有一些限制。以下函数在指标中是被禁止的:
所有为指标设计的函数也在专家顾问和脚本中被禁止:
库不是一个独立的程序,它在调用它的MQL4程序的上下文中执行:脚本、指标或专家顾问。因此,上述限制适用于被调用的库。
指标的加载和卸载
指标在以下情况下加载:
- 指标附加到图表;
- 终端启动(如果指标在终端关闭前已附加到图表);
- 模板加载(如果附加到图表的指标在模板中指定);
- 配置文件更改(如果指标附加到一个配置文件图表);
- 更改图表的符号和/或时间范围,该指标附加到该图表;
- 指标成功重新编译后(如果指标附加到图表);
- 更改指标的输入参数。
指标在以下情况下卸载:
- 从图表中分离指标;
- 终端关闭(如果指标附加到图表);
- 模板加载(如果指标附加到图表);
- 关闭该指标附加到的图表;
- 配置文件更改(如果指标附加到更改配置文件的图表之一);
- 更改该指标附加到的图表的符号和/或时间范围;
- 更改指标的输入参数。
专家顾问的加载和卸载
专家顾问在以下情况下加载:
- 将专家顾问附加到图表;
- 终端启动(如果专家顾问在终端关闭前已附加到图表);
- 模板加载(如果附加到图表的专家顾问在模板中指定);
- 配置文件更改(如果专家顾问附加到某个配置文件图表);
- 连接账户,即使账户号码相同(如果专家顾问在服务器上授权之前已附加到图表)。
专家顾问在以下情况下卸载:
- 从图表中分离专家顾问;
- 如果新的专家顾问附加到图表,且已有其他专家顾问附加到图表,则卸载该专家顾问。
- 终端关闭(如果专家顾问附加到图表);
- 模板加载(如果专家顾问附加到图表);
- 关闭该专家顾问附加到的图表。
- 配置文件更改(如果专家顾问附加到更改配置文件的图表之一);
- 更改终端连接的账户(如果专家顾问在服务器上授权之前已附加到图表);
- 调用ExpertRemove()函数。
如果专家顾问附加到的图表或时间范围发生变化,则不会加载或卸载专家顾问。在这种情况下,客户端终端会在旧的符号/时间范围内调用OnDeinit(),并在新符号/时间范围内调用OnInit()(如果有的话),全局变量和静态变量不会重置。所有在初始化完成之前为专家顾问接收到的事件(OnInit()函数)都会被跳过。
为了更好地理解专家顾问的运作,我们建议编译以下专家顾问的代码,并执行加载/卸载、模板更改、符号更改、时间范围更改等操作:
示例:
//+------------------------------------------------------------------+
//| TestExpert.mq5 |
//| Copyright 2009, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
class CTestClass
{
public:
CTestClass() { Print("CTestClass constructor"); }
~CTestClass() { Print("CTestClass destructor"); }
};
CTestClass global;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//---
Print("Initialization");
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
Print("Deinitialization with reason",reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
脚本的加载和卸载
脚本在附加到图表后立即加载,在完成操作后立即卸载。
当程序被卸载(从图表中删除)时,客户端终端会初始化全局变量并删除事件队列。在这种情况下,初始化意味着重置所有字符串-类型的变量,释放dynamical array对象的分配,并调用其析构函数(如果可用)。
//+------------------------------------------------------------------+
//| TestScript.mq5 |
//| Copyright 2014, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2014, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
class CTestClass
{
public:
CTestClass() { Print("CTestClass constructor"); }
~CTestClass() { Print("CTestClass destructor"); }
};
CTestClass global;
//+------------------------------------------------------------------+
//| Script program initialization function |
//+------------------------------------------------------------------+
void OnInit()
{
Print(__FUNCTION__);
}
//+------------------------------------------------------------------+
//| Script program deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print(__FUNCTION__," reason=",reason);
}
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//---
Print(__FUNCTION__);
}输入参数和源代码编译
如果图表上启动的程序的源代码成功重新编译,其旧版本将从图表中移除,新的编译副本将代替执行。
如果重新编译后输入参数没有更改,则应用之前指定的参数值。否则,使用默认值。
在编辑源代码时,以下情况被认为输入参数集已更改:
- 参数数量发生变化;
- 参数顺序发生变化;
- 参数名称发生变化;
- 一个或多个参数的类型发生变化。
更改任何参数的默认值并不被视为输入参数集的更改。
输入参数集在终端执行系统中明确标识了程序。如果此集合未更改,则可执行文件的新版本被认为保留了程序的全部逻辑和功能。
如果输入参数集发生变化,终端将认为新的可执行文件与之前在图表上启动的程序不兼容。因此,新的重新编译程序将以具有默认值的输入参数集启动。
在其他情况下(包括任何参数的默认值发生变化的情况),重新编译后应用之前指定的参数。
OnInit()预定义函数在任何编译之后都会调用。其目的是正确初始化程序的所有全局和静态变量。程序输入参数值应在OnInit()事件处理程序中正确使用。