第十三章 事件处理
MQL5语言提供对特定预定义事件的处理。处理这些事件的函数应该在MQL5程序中定义:函数名、返回类型、一组参数(如果有),并且它们的类型应该严格地符合事件处理函数的描述。
客户端事件处理程序使用返回和参数类型来识别处理事件的函数。如果某个函数有一些参数或者返回类型与下面的描述不符,那么这样的函数就不可以被用于处理事件。
函数 | 功能 |
---|---|
OnStart | 当Start事件发生时调用这个函数,来执行脚本中设定的操作 |
OnInit | 当Init事件发生时在指标和EA中调用这个函数,来初始化启动的MQL5程序 |
OnDeinit | 当Deinit事件发生时在指标和EA中调用这个函数,来去初始化启动的MQL5程序 |
OnTick | 当NewTick事件发生时在EA中调用这个函数,来处理一个新报价 |
OnCalculate | 当Calculate事件发生时在指标中调用这个函数,来处理价格数据的变化 |
OnTimer | 当Timer周期性事件在固定时间间隔内由程序端生成期间,在指标和EA中调用这个函数 |
OnTrade | 当Trade事件在交易服务器上交易操作结束时生成期间,在EA中调用这个函数 |
OnTradeTransaction | 当TradeTransaction事件发生时在EA中调用这个函数,来处理一个交易请求执行结果 |
OnBookEvent | 当BookEvent事件发生时在EA中调用这个函数,来处理市场深度中的变化 |
OnChartEvent | 当ChartEvent事件发生时在指标和EA中调用这个函数,来处理由用户或者MQL5程序所做的图表更改 |
OnTester | 当Tester事件发生时在EA中调用这个函数,以便在对历史数据测试EA之后执行必要的操作 |
OnTesterInit | 当TesterInit事件发生时在EA中调用这个函数,以便在策略测试优化之前执行必要的操作 |
OnTesterDeinit | 在策略测试EA优化之后,当TesterDeinit事件发生时在EA中调用这个函数。 |
OnTesterPass | 当TesterPas事件发生时在EA中调用这个函数,以便在策略测试中处理EA优化期间到达的新数据框架 |
客户端将传入事件发送到相应的打开图表中。此外,事件也可以通过图表(图表事件)或mql5程序(自定义事件)来生成。通过设置CHART_EVENT_OBJECT_CREATE和CHART_EVENT_OBJECT_DELETE图表属性可以启用/禁用生成图形对象创建/删除事件。每个mql5应用程序和图表都有其自己的事件队列,所有新到达的事件也都包括其中。
一个程序只从其所运行的图表中获取事件。所有事件都按照它们的接收顺序来处理。如果这个队列已经包括NewTick事件,或者这个事件正在处理阶段,那么新的NewTick事件就不能添加到mql5应用程序队列中。同样,如果ChartEvent已在mql5程序序列或者这种事件正被处理,那么这种类型的新事件都不可放在队列之中。Timer事件处理也以相同的方式进行――如果Timer事件已在队列中或者正被处理中,那么队列中则不设置新的timer事件
事件队列具有一定限制但充足的规模,所以对于一个正确的开发程序来说,不可能出现队列溢出的情况。当队列溢出时,新事件会被舍弃,而不会将其设置为队列。
强烈建议不要使用无限循环来处理事件。可能的例外就是处理单独的Start事件的脚本。
程序库不处理任何事件。
# 13.1 OnStart
当Start事件发生时调用这个函数。这个函数旨在一次性执行脚本中实施的操作。有两种函数类型。
返回结果的版本
int OnStart(void);
返回值 int类型的值显示在“日志”选项卡中。
脚本执行完成之后,在程序端日志中创建了“脚本script_name已移除(结果代码N)”条目。在这里,N是OnStart()函数的返回值。
建议使用返回执行结果的OnStart()调用,因为它不仅允许脚本执行,还可以返回错误代码或其他有用的数据来分析脚本执行结果。
没有结果返回的版本只为与旧代码兼容而保留。不建议使用
void OnStart(void);
注意 OnStart()是处理脚本事件的唯一函数。不会有其他事件被发送至脚本。Start事件不被传递到EA和自定义指标。
脚本样例:
//--- 用于处理颜色的宏
#define XRGB(r,g,b) (0xFF000000|(uchar(r) << 16)|(uchar(g) << 8)|uchar(b))
#define GETRGB(clr) ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| 脚本程序起始函数 |
//+------------------------------------------------------------------+
void OnStart()
{
//--- 设置一个下行的蜡烛图颜色
Comment("Set a downward candle color");
ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,GetRandomColor());
ChartRedraw(); // 无需等候新报价,立即更新图表
Sleep(1000); // 暂停1秒,查看所有的变化
//--- 设置一个上行的蜡烛图颜色
Comment("Set an upward candle color");
ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置背景颜色
Comment("Set the background color");
ChartSetInteger(0,CHART_COLOR_BACKGROUND,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置买价线的颜色
Comment("Set color of Ask line");
ChartSetInteger(0,CHART_COLOR_ASK,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置卖价线的颜色
Comment("Set color of Bid line");
ChartSetInteger(0,CHART_COLOR_BID,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置下行柱形图和下行蜡烛图框架的颜色
Comment("Set color of a downward bar and a downward candle frame");
ChartSetInteger(0,CHART_COLOR_CHART_DOWN,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置图表线和Doji蜡烛图的颜色
Comment("Set color of a chart line and Doji candlesticks");
ChartSetInteger(0,CHART_COLOR_CHART_LINE,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置上行柱形图和上行蜡烛图框架的颜色
Comment("Set color of an upward bar and an upward candle frame");
ChartSetInteger(0,CHART_COLOR_CHART_UP,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置坐标轴、比例和OHLC线的颜色
Comment("Set color of axes, scale and OHLC line");
ChartSetInteger(0,CHART_COLOR_FOREGROUND,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置一个网格颜色
Comment("Set a grid color");
ChartSetInteger(0,CHART_COLOR_GRID,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置最后价格线
Comment("Set Last price color");
ChartSetInteger(0,CHART_COLOR_LAST,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置止损和获利水平的颜色
Comment("Set color of Stop Loss and Take Profit order levels");
ChartSetInteger(0,CHART_COLOR_STOP_LEVEL,GetRandomColor());
ChartRedraw();
Sleep(1000);
//--- 设置交易量的颜色和市场进入水平
Comment("Set color of volumes and market entry levels");
ChartSetInteger(0,CHART_COLOR_VOLUME,GetRandomColor());
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 返回一个随机生成的颜色 |
//+------------------------------------------------------------------+
color GetRandomColor()
{
color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
return clr;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
另见 事件处理函数、程序运行、客户端事件
# 13.2 OnInit
当Init事件发生时在指标和EA中调用这个函数。它被用于初始化运行中的MQL5程序。有两种函数类型。
返回结果的版本
int OnInit(void);
返回值 int类型值,0意味着成功初始化。
建议使用返回执行结果的OnInit()调用,因为它不仅可以程序初始化,还可以在早期程序终止的情况下返回一个错误代码。
没有结果返回的版本只为与旧代码兼容而保留。不建议使用
void OnInit(void);
注意 加载EA或指标之后会立即生成Init事件。不为脚本生成该事件。OnInit()函数被用来初始化MQL5程序。如果OnInit()有一个int类型的返回值,那么非零的返回代码意味着初始化失败,且生成REASON_INITFAILED去初始化原因代码的Deinit事件。
void类型的OnInit()函数始终表示初始化成功,且不建议使用。
为了优化EA 输入,建议使用来自ENUM_INIT_RETCODE枚举值作为返回代码。 这些值旨在建立优化过程管理,包括选择最合适的测试代理。在启动测试之前,在EA初始化过程中,可以使用TerminalInfoInteger()函数请求关于代理配置和资源(核心数量、空闲内存量等)的数据。根据所获得的数据,您既可以允许使用测试代理,也可以禁止它优化EA。
ID | 描述 |
---|---|
INIT_SUCCEEDED | 初始化成功,EA测试可以继续。 这个代码意同零值――测试中的EA初始化成功。 |
INIT_FAILED | 初始化失败。由于不可避免的错误,因此继续测试没有意义。 例如,不能创建EA操作所需的指标。 这个值返回意同返回非零的值――测试中的EA初始化失败。 |
INIT_PARAMETERS_INCORRECT | 旨在表示程序员一组不正确的输入参数。在通用的优化表格中,该返回代码的结果字符串以红色突出显示。 不执行这组EA输入的测试。代理已准备好接收新任务。 当收到该值时,策略测试不将此任务传递到另一个代理重复执行。 |
INIT_AGENT_NOT_SUITABLE | 在初始化过程中没有程序执行错误。然而,出于某些原因,代理不适合进行测试。例如,没有足够的RAM,没有OpenCL support等。 返回该代码之后,直至这个优化结束,代理才会再接收任务 |
ID
描述
INIT_SUCCEEDED
初始化成功,EA测试可以继续。
这个代码意同零值――测试中的EA初始化成功。
INIT_FAILED
初始化失败。由于不可避免的错误,因此继续测试没有意义。例如,不能创建EA操作所需的指标。
这个值返回意同返回非零的值――测试中的EA初始化失败。
INIT_PARAMETERS_INCORRECT
旨在表示程序员一组不正确的输入参数。在通用的优化表格中,该返回代码的结果字符串以红色突出显示。
不执行这组EA输入的测试。代理已准备好接收新任务。 当收到该值时,策略测试不将此任务传递到另一个代理重复执行。
INIT_AGENT_NOT_SUITABLE
在初始化过程中没有程序执行错误。然而,出于某些原因,代理不适合进行测试。例如,没有足够的RAM,没有OpenCL support等。
返回该代码之后,直至这个优化结束,代理才会再接收任务。
使用OnInit()在测试中返回INIT_FAILED/INIT_PARAMETERS_INCORRECT有一些再优化EA时需要考虑的特性:
OnInit()返回INIT_PARAMETERS_INCORRECT的参数集被认为不适合进行测试,并且在遗传优化期间不用于获取下一个群集。在寻找最佳EA参数时,太多“废弃”参数集可能导致错误的结果。搜索EA假设优化准则函数平滑,在整个输入参数上没有间隙。
如果OnInit()返回INIT_FAILED,这意味着测试无法启动,EA从代理内存中卸载。再次加载EA,以使用一组新参数执行下一个传递。与调用TesterStop()相比,启动下一次优化传递需要更多的时间。
EA的OnInit()样例函数
//--- 输入参数
input int ma_period=20; //移动平均线周期
//--- 在EA中使用的指标句柄
int indicator_handle;
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 检查ma_period有效性
if(ma_period<=0)
{
PrintFormat("Invalid ma_period input value: %d",ma_period);
return (INIT_PARAMETERS_INCORRECT);
}
//--- 优化过程中
if(MQLInfoInteger(MQL_OPTIMIZATION))
{
//--- 为代理检查可用的RAM
int available_memory_mb=TerminalInfoInteger(TERMINAL_MEMORY_TOTAL);
if(available_memory_mb<2000)
{
PrintFormat("Insufficient memory for the test agent: %d MB",
available_memory_mb);
return (INIT_AGENT_NOT_SUITABLE);
}
}
//--- 检查指标
indicator_handle=iCustom(_Symbol,_Period,"My_Indicator",ma_period);
if(indicator_handle==INVALID_HANDLE)
{
PrintFormat("Failed to generate My_Indicator handle. Error code %d",
GetLastError());
return (INIT_FAILED);
}
//--- EA初始化成功
return(INIT_SUCCEEDED);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
另见 OnDeinit、事件处理函数、程序运行、客户端事件、变量的初始化、创建和删除对象
# 13.3 OnDeinit
当Deinit事件发生时在指标和EA中调用这个函数。它被用于去初始化运行中的MQL5程序。
int OnDeinit(
const int reason //去初始化原因代码
);
2
3
参数 reason
[in] 去初始化原因代码。
返回值 无返回值
注意 在以下情况下,为EA和指标生成Deinit事件:
由于mql5程序附加的交易品种或图表周期发生变化而重新初始化之前;
由于输入变化而重新初始化之前;
卸载mql5程序之前。
reason参数可能有以下值:
常量 | 值 | 描述 |
---|---|---|
REASON_PROGRAM | 0 | EA已停止调用ExpertRemove()函数的工作 |
REASON_REMOVE | 1 | 程序从图表移除 |
REASON_RECOMPILE | 2 | 程序重新编译 |
REASON_CHARTCHANGE | 3 | 交易品种或图表周期被改变 |
REASON_CHARTCLOSE | 4 | 图表已关闭 |
REASON_PARAMETERS | 5 | 由用户更改的输入参数 |
REASON_ACCOUNT | 6 | 由于账户设置的变化,另一个账户已被激活或已被重新连接到交易服务器 |
REASON_TEMPLATE | 7 | 应用另一个图表模板 |
REASON_INITFAILED | 8 | OnInit()处理程序返回一个非零值 |
REASON_CLOSE | 9 | 程序端已关闭 |
EA去初始化原因代码可通过UninitializeReason()函数接收或者从预定义_UninitReason变量接收。
EA的OnInit()和OnDeinit()示例函数
input int fake_parameter=3; // 无用的参数
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 获取程序被编译的构建号
Print(__FUNCTION__," Build #",__MQLBUILD__);
//--- 在OnInit()中还可以获得重置原因代码
Print(__FUNCTION__," Deinitialization reason code can be received during the EA reset");
//--- 获得去初始化原因代码的第一个方式
Print(__FUNCTION__," _UninitReason = ",getUninitReasonText(_UninitReason));
//--- 获得去初始化原因代码的第二个方式
Print(__FUNCTION__," UninitializeReason() = ",getUninitReasonText(UninitializeReason()));
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA交易去初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 获得去初始化原因代码的第一个方式
Print(__FUNCTION__," Deinitialization reason code = ",reason);
//--- 获得去初始化原因代码的第二个方式
Print(__FUNCTION__," _UninitReason = ",getUninitReasonText(_UninitReason));
//--- 获得去初始化原因代码的第三个方式
Print(__FUNCTION__," UninitializeReason() = ",getUninitReasonText(UninitializeReason()));
}
//+------------------------------------------------------------------+
//| 返回去初始化原因代码的文本描述 |
//+------------------------------------------------------------------+
string getUninitReasonText(int reasonCode)
{
string text="";
//---
switch(reasonCode)
{
case REASON_ACCOUNT:
text="Account was changed";break;
case REASON_CHARTCHANGE:
text="Symbol or timeframe was changed";break;
case REASON_CHARTCLOSE:
text="Chart was closed";break;
case REASON_PARAMETERS:
text="Input-parameter was changed";break;
case REASON_RECOMPILE:
text="Program "+__FILE__+" was recompiled";break;
case REASON_REMOVE:
text="Program "+__FILE__+" was removed from chart";break;
case REASON_TEMPLATE:
text="New template was applied to chart";break;
default:text="Another reason";
}
//---
return text;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
另见 OnInit、 事件处理函数、程序运行、客户端事件、去初始化原因代码、可视范围和变量的生命周期、 创建和删除对象
# 13.4 OnTick
当NewTick事件发生时在EA中调用这个函数,来处理一个新报价。
void OnTick(void);
返回值 无返回值
注意 NewTick事件只在收到一个新报价时为EA生成,以获得EA所附加的图表的交易品种。在自定义指标或脚本中定义OnTick()函数没有意义,因为没有为其生成NewTick事件。
Tick事件只为EA生成,但这并不意味着EA必须包含OnTick()函数的功能,因为除了NewTick以外,还为EA生成Timer、BookEvent和ChartEvent事件。
所有事件都按照它们的接收顺序依次处理。如果这个队列已经包括NewTick事件,或者这个事件正在处理阶段,那么新的NewTick事件就不能添加到mql5应用程序队列中。
无论是否启用自动交易(自动交易按键),都会生成NewTick事件。禁用自动交易意味着只禁止从EA发送交易请求。EA操作不会停止。
通过按下自动交易按键来禁用自动交易并不会中断OnTick()函数的当前执行。
在OnTick()函数中,显示整个交易逻辑特性的EA示例
//+------------------------------------------------------------------+
//| TradeByATR.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample EA trading in the \"explosive\" candle direction"
#property description "\"Explosive\" candle has the body size exceeding k*ATR"
#property description "The \"revers\" parameter reverses the signal direction"
input double lots=0.1; // 交易量手数
input double kATR=3; // ATR中的信号蜡烛长度
input int ATRperiod=20; // ATR指标周期
input int holdbars=8; // 保持持仓的柱形图数量
input int slippage=10; // 可允许滑移
input bool revers=false; //反向信号?
input ulong EXPERT_MAGIC=0; // EA幻数
//--- 用于存储ATR指标句柄
int atr_handle;
//--- 在这里,我们将存储ATR最后值和蜡烛体
double last_atr,last_body;
datetime lastbar_timeopen;
double trade_lot;
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 初始化全局变量
last_atr=0;
last_body=0;
//--- 设置正确的交易量
double min_lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
trade_lot=lots>min_lot? lots:min_lot;
//--- 创建ATR指标句柄
atr_handle=iATR(_Symbol,_Period,ATRperiod);
if(atr_handle==INVALID_HANDLE)
{
PrintFormat("%s: failed to create iATR, error code %d",__FUNCTION__,GetLastError());
return(INIT_FAILED);
}
//--- EA成功初始化
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA交易去初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 通知EA操作结束代码
Print(__FILE__,": Deinitialization reason code = ",reason);
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 交易信号
static int signal=0; // +1表示买入信号, -1 表示卖出信号
//--- 检查和关闭‘holdbars’柱形图之前开仓的旧持仓
ClosePositionsByBars(holdbars,slippage,EXPERT_MAGIC);
//--- 检查新柱形图
if(isNewBar())
{
//--- 检查信号是否存在
signal=CheckSignal();
}
//--- 如果持有单边持仓,跳过信号-等候直至关闭
if(signal!=0 && PositionsTotal()>0 && (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_NETTING)
{
signal=0;
return; // 退出NewTick事件处理程序,并在新柱形图出现之前不要进入市场
}
//--- 对于锁仓账户,每个持仓都是单独持仓和平仓
if(signal!=0)
{
//--- 买入信号
if(signal>0)
{
PrintFormat("%s: Buy signal! Revers=%s",__FUNCTION__,string(revers));
if(Buy(trade_lot,slippage,EXPERT_MAGIC))
signal=0;
}
//--- 卖出信号
if(signal<0)
{
PrintFormat("%s: Sell signal! Revers=%s",__FUNCTION__,string(revers));
if(Sell(trade_lot,slippage,EXPERT_MAGIC))
signal=0;
}
}
//--- OnTick函数结束
}
//+------------------------------------------------------------------+
//| 检查新的交易信号 |
//+------------------------------------------------------------------+
int CheckSignal()
{
//--- 0 意味着没有信号
int res=0;
//--- 获得倒数第二个完整柱形图的ATR值(柱形图索引是2)
double atr_value[1];
if(CopyBuffer(atr_handle,0,2,1,atr_value)!=-1)
{
last_atr=atr_value[0];
//--- 获得最后关闭的柱形图的MqlRates类型数组数据
MqlRates bar[1];
if(CopyRates(_Symbol,_Period,1,1,bar)!=-1)
{
//--- 计算最后完整柱形图的柱体大小
last_body=bar[0].close-bar[0].open;
//--- 如果最后柱形图的柱体(索引为1)超过的之前ATR的值(柱形图索引为2),那么会收到一个交易信号
if(MathAbs(last_body)>kATR*last_atr)
res=last_body>0?1:-1; // 向上蜡烛图的正值
}
else
PrintFormat("%s: Failed to receive the last bar! Error",__FUNCTION__,GetLastError());
}
else
PrintFormat("%s: Failed to receive ATR indicator value! Error",__FUNCTION__,GetLastError());
//--- 如果启用了反向交易模式
res=revers?-res:res; // 在必要时反转信号(返回-1而不是1,反之亦然)
//--- 返回一个交易信号值
return (res);
}
//+------------------------------------------------------------------+
//| 当新柱形图出现时返回'true' |
//+------------------------------------------------------------------+
bool isNewBar(const bool print_log=true)
{
static datetime bartime=0; //存储当前柱形图的开盘时间
//--- 获得零柱的开盘时间
datetime currbar_time=iTime(_Symbol,_Period,0);
//--- 如果开盘时间更改,则新柱形图出现
if(bartime!=currbar_time)
{
bartime=currbar_time;
lastbar_timeopen=bartime;
//--- 在日志中显示新柱形图开盘时间的数据
if(print_log && !(MQLInfoInteger(MQL_OPTIMIZATION)||MQLInfoInteger(MQL_TESTER)))
{
//--- 显示新柱形图开盘时间的信息
PrintFormat("%s: new bar on %s %s opened at %s",__FUNCTION__,_Symbol,
StringSubstr(EnumToString(_Period),7),
TimeToString(TimeCurrent(),TIME_SECONDS));
//--- 获取关于最后报价的数据
MqlTick last_tick;
if(!SymbolInfoTick(Symbol(),last_tick))
Print("SymbolInfoTick() failed, error = ",GetLastError());
//--- 显示最后报价的时间,精确至毫秒
PrintFormat("Last tick was at %s.%03d",
TimeToString(last_tick.time,TIME_SECONDS),last_tick.time_msc%1000);
}
//--- 我们有一个新柱形图
return (true);
}
//--- 没有新柱形图
return (false);
}
//+------------------------------------------------------------------+
//| 以指定交易量和市场价买入 |
//+------------------------------------------------------------------+
bool Buy(double volume,ulong deviation=10,ulong magicnumber=0)
{
//--- 以市场价买入
return (MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber));
}
//+------------------------------------------------------------------+
//| 以指定交易量和市场价卖出 |
//+------------------------------------------------------------------+
bool Sell(double volume,ulong deviation=10,ulong magicnumber=0)
{
//--- 以市场价卖出
return (MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber));
}
//+------------------------------------------------------------------+
//| 在柱形图中根据持有时间平仓 |
//+------------------------------------------------------------------+
void ClosePositionsByBars(int holdtimebars,ulong deviation=10,ulong magicnumber=0)
{
int total=PositionsTotal(); // 持仓数量
//--- 重复持仓
for(int i=total-1; i>=0; i--)
{
//--- 持仓参数
ulong position_ticket=PositionGetTicket(i); // 持仓单号
string position_symbol=PositionGetString(POSITION_SYMBOL); //交易品种
ulong magic=PositionGetInteger(POSITION_MAGIC); // 持仓幻数
datetime position_open=(datetime)PositionGetInteger(POSITION_TIME); // 持仓开盘时间
int bars=iBarShift(_Symbol,PERIOD_CURRENT,position_open)+1; // 开仓之前有多少柱形图
//--- 如果持仓的生命周期很长,则幻数和交易品种匹配
if(bars > holdtimebars && magic==magicnumber && position_symbol==_Symbol)
{
int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // 小数位数
double volume=PositionGetDouble(POSITION_VOLUME); // 持仓交易量
ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // 持仓类型
string str_type=StringSubstr(EnumToString(type),14);
StringToLower(str_type); //纠正消息格式的小写文本案例
PrintFormat("Close position #%d %s %s %.2f",
position_ticket,position_symbol,str_type,volume);
//--- 设置一个订单类型并发送交易请求
if(type==POSITION_TYPE_BUY)
MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber,position_ticket);
else
MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber,position_ticket);
}
}
}
//+------------------------------------------------------------------+
//| 准备和发送交易请求 |
//+------------------------------------------------------------------+
bool MarketOrder(ENUM_ORDER_TYPE type,double volume,ulong slip,ulong magicnumber,ulong pos_ticket=0)
{
//--- 声明和初始化结构
MqlTradeRequest request={0};
MqlTradeResult result={0};
double price=SymbolInfoDouble(Symbol(),SYMBOL_BID);
if(type==ORDER_TYPE_BUY)
price=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
//--- 请求参数
request.action =TRADE_ACTION_DEAL; // 交易操作类型
request.position =pos_ticket; // 关闭情况下的持仓单号
request.symbol =Symbol(); // 交易品种
request.volume =volume; // 交易量
request.type =type; // 订单类型
request.price =price; // 交易价格
request.deviation=slip; // 可允许的价格偏差
request.magic =magicnumber; // 订单幻数
//--- 发送请求
if(!OrderSend(request,result))
{
//--- 显示数据失败
PrintFormat("OrderSend %s %s %.2f at %.5f error %d",
request.symbol,EnumToString(type),volume,request.price,GetLastError());
return (false);
}
//--- 通知成功操作
PrintFormat("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order);
return (true);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
另见 事件处理函数、程序运行、客户端事件、OnTimer、 OnBookEvent、OnChartEvent
# 13.5 OnCalculate
当Calculate事件发生时在指标中调用这个函数,来处理价格数据的变化。有两种函数类型。一个指标内只可以使用其中一种。
基于数据数组的计算
int OnCalculate(
const int rates_total, // price[]数组大小
const int prev_calculated, // 在前一个调用中处理过的柱形图数量
const int begin, //price[]数组中,有意义数据开始的索引编号
const double& price[] // 计算值数组
);
2
3
4
5
6
基于当前时间周期时间序列的计算
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& tick_volume[], // 报价量数组
const long& volume[], // 真实交易量数组
const int& spread[] // 点差数组
);
2
3
4
5
6
7
8
9
10
11
12
参数 rates_total
[in] 可用于指标计算的price[]数组或输入序列的大小。在第二个函数类型中,当参数值对应于图表中柱形图数量时,启动这种函数类型。
prev_calculated
[in] 包含上一次调用期间OnCalculate()函数的返回值。它的设计旨在跳过自该函数上次启动后没有改变的柱形图。
begin
[in] price[]数组中,有意义数据开始的索引编号。它允许您跳过缺失或初始数据,因为没有正确的值。
price[]
[in] 计算值数组。其中一个价格时间序列或计算过的指标缓冲区可以作为price[]数组进行传递。为计算而传递的数据类型可以使用_AppliedTo预定义变量来定义。
time{}
[in] 柱形图开盘时间值的数组。
open[]
[in] 开盘价格值的数组。
high[]
[in] 最高价格值的数组。
low[]
[in] 最低价格值的数组。
close[]
[in] 收盘价格值的数组。
tick_volume[]
[in] 报价量值的数组。
volume[]
[in] 交易量值的数组。
spread[]
[in] 柱形图点差值的数组。
返回值 int类型值将在下一次函数调用期间作为prev_calculated参数被传递。
注意 如果OnCalculate()函数等于零,则没有指标值显示在客户的数据窗口中。 如果价格数据在最后一次调用OnCalculate()函数之后发生了变化(已加载更深层历史记录或已填充历史记录空白),那么程序端本身则将prev_calculated输入参数设为零。
若要在time[]、open[]、high[]、low[]、 close[]、tick_volume[]、volume[]和spread[]数组中定义索引方向,请调用ArrayGetAsSeries()函数。为了不依赖于默认值,请为数组调用ArraySetAsSeries()函数来进行处理。
当使用第一种函数类型时,在启动指标时用户选择必要的时间序列或指标作为“参数”选项卡中的price[]数据。为此,请在“应用于”字段的下拉列表中指定必要的元素。
若要获取其他mql5程序的自定义指标值,则使用iCustom()函数。它返回后续操作的指标句柄。也可以指定所需的price [] 数组或另一个指标的句柄。这个参数应被传递到自定义指标输入变量列表的最后。
有必要使用OnCalculate()函数返回值和prev_calculated第二个输入参数之间的连接。当调用函数时,prev_calculated函数包含了上一次调用期间OnCalculate()函数的返回值。这使得为计算自定义指标而实现资源节约算法成为可能,以避免重复计算自上一次启动该函数以来没有改变的柱形图。
样例指标
//+------------------------------------------------------------------+
//| OnCalculate_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample Momentum indicator calculation"
//---- 指标设置
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 Blue
//---- 输入
input int MomentumPeriod=14; // 计算周期
//---- 指标缓存区
double MomentumBuffer[];
//--- 存储计算周期的全局变量
int IntPeriod;
//+------------------------------------------------------------------+
//| 自定义指标初始化函数 |
//+------------------------------------------------------------------+
void OnInit()
{
//--- 检查输入参数
if(MomentumPeriod<0)
{
IntPeriod=14;
Print("Period parameter has an incorrect value. The following value is to be used for calculations ",IntPeriod);
}
else
IntPeriod=MomentumPeriod;
//---- 缓冲区
SetIndexBuffer(0,MomentumBuffer,INDICATOR_DATA);
//---- 显示在“数据窗口”和子窗口的指标名称
IndicatorSetString(INDICATOR_SHORTNAME,"Momentum"+"("+string(IntPeriod)+")");
//--- 设置柱形图绘制的开始索引
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,IntPeriod-1);
//--- 将0.0设置为未绘制的空值
PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//--- 显示的指标精确性
IndicatorSetInteger(INDICATOR_DIGITS,2);
}
//+------------------------------------------------------------------+
//| 动量指标计算 |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, // price[] array size
const int prev_calculated, // 之前处理的柱形图的数量
const int begin, //重要数据起点
const double &price[]) // 值数组处理
{
//--- 初始持仓计算
int StartCalcPosition=(IntPeriod-1)+begin;
//---- 如果计算数据不足
if(rates_total StartCalcPosition)
return(0); // 以零值推出 - 这个指标没有被计算
//--- 正确绘制开始
if(begin>0)
PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,StartCalcPosition+(IntPeriod-1));
//--- 开始计算,定义开始位置
int pos=prev_calculated-1;
if(pos StartCalcPosition)
pos=begin+IntPeriod;
//--- 主计算循环
for(int i=pos;i<rates_total && !IsStopped();i++)
MomentumBuffer[i]=price[i]*100/price[i-IntPeriod];
//--- OnCalculate执行完成。返回后续调用的新prev_calculated值
return(rates_total);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
另见 ArrayGetAsSeries、ArraySetAsSeries、iCustom、事件处理函数、程序运行、客户端事件、访问时间序列和指标
# 13.6 OnTimer
当Timer事件在固定时间间隔内由程序端生成期间,在EA中调用这个函数。
void OnTimer(void);
返回值 无返回值
注意 Timer事件是由客户端为EA定期生成,使用EventSetTimer()函数来激活计时器。通常,这个函数在OnInit()函数中调用。EA停止工作时,应使用通常在OnDeinit()函数中被调用的EventKillTimer()消除计时器。
每个EA和每个指标都使用其自己的计时器,并从该计时器单独接收事件。mql5程序关闭期间,如果计时器已经创建但没有被EventKillTimer()函数禁用,那么计时器则被强制销毁。
如果您不满足于每秒一次,需要更频繁地接收计时器事件,请使用EventSetMillisecondTimer()来创建高分辨率计时器。
策略测试中使用1000毫秒最小间隔。一般来说,当计时器周期减少时,测试时间会增加,因为计时器事件的处理程序会更频繁地被调用。在实时模式下工作时,由于硬件的限制,计时器事件在10-16毫秒内生成不超过1次。
每个程序只能启动一个计时器。每个mql5应用程序和图表都有其自己的事件队列,所有新到达的事件也都包括其中。如果这个队列已经包括Timer事件,或者这个事件正在处理阶段,那么新的Timer事件就不能添加到mql5应用程序队列中。
EA样例,OnTimer()处理程序
//+------------------------------------------------------------------+
//| OnTimer_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Example of using the timer for calculating the trading server time"
#property description "It is recommended to run the EA at the end of a trading week before the weekend"
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 创建一个1秒周期的计时器
EventSetTimer(1);
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA交易去初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 完成工作后销毁计时器
EventKillTimer();
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| Timer函数 |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- OnTimer()首次调用的时间
static datetime start_time=TimeCurrent();
//--- 第一次调用OnTimer()期间交易服务器的时间
static datetime start_tradeserver_time=0;
//--- 计算交易服务器时间
static datetime calculated_server_time=0;
//--- 本地PC时间
datetime local_time=TimeLocal();
//--- 目前估计的交易服务器时间
datetime trade_server_time=TimeTradeServer();
//--- 如果由于某种原因而未知服务器时间,则提前退出
if(trade_server_time==0)
return;
//--- 如果尚未设置初始交易服务器的值
if(start_tradeserver_time==0)
{
start_tradeserver_time=trade_server_time;
//--- 设置交易服务器的计算值
Print(trade_server_time);
calculated_server_time=trade_server_time;
}
else
{
//--- 增加OnTimer()第一次调用的时间
if(start_tradeserver_time!=0)
calculated_server_time=calculated_server_time+1;;
}
//---
string com=StringFormat(" Start time: %s\r\n",TimeToString(start_time,TIME_MINUTES|TIME_SECONDS));
com=com+StringFormat(" Local time: %s\r\n",TimeToString(local_time,TIME_MINUTES|TIME_SECONDS));
com=com+StringFormat("TimeTradeServer time: %s\r\n",TimeToString(trade_server_time,TIME_MINUTES|TIME_SECONDS));
com=com+StringFormat(" EstimatedServer time: %s\r\n",TimeToString(calculated_server_time,TIME_MINUTES|TIME_SECONDS));
//--- 显示图表上所有计数器的值
Comment(com);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
另见
EventSetTimer、EventSetMillisecondTimer、EventKillTimer、 GetTickCount、GetMicrosecondCount、客户端事件
# 13.7 OnTrade
当Trade事件发生时在EA中调用这个函数。这个函数旨在处理订单、持仓和交易列表的变化。
void OnTrade(void);
返回值 无返回值
注意 OnTrade()只为EA交易调用。即使您在指标和脚本中添加了具有相同名称和类型的函数,也不能在这些指标和脚本中使用此函数。
对于任何交易操作(下达挂单、开仓/平仓、下止损单、激活挂单等),订单和交易的历史和/或持仓和当前订单的列表都被适当更改。
当处理一个订单时,交易服务器向程序端发送一条关于传入Trade事件的消息。若要从历史记录中检索订单和交易的相关数据,需要先使用HistorySelect()执行交易历史请求。
如果出现以下情况,则服务器生成交易事件: 更改活跃订单, 更改持仓, 更改交易, 更改交易历史。
每一个 Trade事件都可能作为一个或多个交易请求的结果出现。交易请求使用OrderSend()或OrderSendAsync(),被发送到服务器。每个请求都可能导致多个交易事件。您不能依赖“一个请求,一个交易事件”的声明,因为事件的处理可能分为几个阶段执行,并且每个操作都可能更改订单的状态、持仓和交易历史记录。
OnTrade()处理程序在适当调用OnTradeTransaction()之后被调用。通常,在OnTrade ()和OnTradeTransaction ()调用的数量上没有确切的相关性。OnTrade()一次调用对应一次或多次的OnTradeTransaction调用。
EA样例,OnTrade()处理程序
//+------------------------------------------------------------------+
//| OnTrade_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
input int days=7; //按天计算的交易历史深度
//--- 在全局范围内设置交易历史的界限
datetime start; // 缓存中交易历史的开始日期
datetime end; // 缓存中交易历史的结束日期
//--- 全局计数器
int orders; // 活跃订单数量
int positions; // 持仓数量
int deals; // 交易历史缓存中的交易数量
int history_orders; // 交易历史缓存中的订单数量
bool started=false; // 计数器相关性标识
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//---
end=TimeCurrent();
start=end-days*PeriodSeconds(PERIOD_D1);
PrintFormat("Limits of the history to be loaded: start - %s, end - %s",
TimeToString(start),TimeToString(end));
InitCounters();
//---
return(0);
}
//+------------------------------------------------------------------+
//| 持仓、订单和交易计数器的初始化 |
//+------------------------------------------------------------------+
void InitCounters()
{
ResetLastError();
//--- 加载历史
bool selected=HistorySelect(start,end);
if(!selected)
{
PrintFormat("%s. Failed to load history from %s to %s to cache. Error code: %d",
__FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
return;
}
//--- 获取当前值
orders=OrdersTotal();
positions=PositionsTotal();
deals=HistoryDealsTotal();
history_orders=HistoryOrdersTotal();
started=true;
Print("Counters of orders, positions and deals successfully initialized");
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
if(started) SimpleTradeProcessor();
else InitCounters();
}
//+------------------------------------------------------------------+
//| 当Trade事件到达时被调用 |
//+------------------------------------------------------------------+
void OnTrade()
{
if(started) SimpleTradeProcessor();
else InitCounters();
}
//+------------------------------------------------------------------+
//| 处理交易和历史变化的示例 |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
{
end=TimeCurrent();
ResetLastError();
//--- 从指定的时间间隔下载交易历史到程序缓存
bool selected=HistorySelect(start,end);
if(!selected)
{
PrintFormat("%s. Failed to load history from %s to %s to cache. Error code: %d",
__FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
return;
}
//--- 获取当前值
int curr_orders=OrdersTotal();
int curr_positions=PositionsTotal();
int curr_deals=HistoryDealsTotal();
int curr_history_orders=HistoryOrdersTotal();
//--- 检查活跃订单的数量是否发生变化
if(curr_orders!=orders)
{
//--- 活跃订单的数量已经发生变化
PrintFormat("Number of orders has been changed. Previous value is %d, current value is %d",
orders,curr_orders);
//--- 更新值
orders=curr_orders;
}
//--- 持仓数量的变化
if(curr_positions!=positions)
{
//--- 持仓的数量已经发生变化
PrintFormat("Number of positions has been changed. Previous value is %d, current value is %d",
positions,curr_positions);
//--- 更新值
positions=curr_positions;
}
//--- 交易历史缓存中的交易数量的变化
if(curr_deals!=deals)
{
//--- 交易历史缓存中的交易数量已经发生变化
PrintFormat("Number of deals has been changed. Previous value is %d, current value is %d",
deals,curr_deals);
//--- 更新值
deals=curr_deals;
}
//--- 交易历史缓存中的历史订单数量的变化
if(curr_history_orders!=history_orders)
{
//--- 交易历史缓存中的历史订单数量已经发生变化
PrintFormat("Number of orders in history has been changed. Previous value is %d, current value is %d",
history_orders,curr_history_orders);
//--- 更新值
history_orders=curr_history_orders;
}
//--- 检查是否有必要更改缓存中请求的交易历史的限制
CheckStartDateInTradeHistory();
}
//+------------------------------------------------------------------+
//| 更改请求交易历史的开始日期 |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
{
//--- 初始时间间隔,如果我们现在开始工作
datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- 确保交易历史的开始限制没有消失
//--- 预定日期超过1天
if(curr_start-start>PeriodSeconds(PERIOD_D1))
{
//--- 修正在缓存区载入历史的开始日期
start=curr_start;
PrintFormat("New start limit of the trade history to be loaded: start => %s",
TimeToString(start));
//--- 现在重新载入更新间隔的交易历史
HistorySelect(start,end);
//--- 为进一步比较,修正历史中交易和订单的计数器
history_orders=HistoryOrdersTotal();
deals=HistoryDealsTotal();
}
}
//+------------------------------------------------------------------+
/* Sample output:
Limits of the history to be loaded: start - 2018.07.16 18:11, end - 2018.07.23 18:11
The counters of orders, positions and deals are successfully initialized
Number of orders has been changed. Previous value 0, current value 1
Number of orders has been changed. Previous value 1, current value 0
Number of positions has been changed. Previous value 0, current value 1
Number of deals has been changed. Previous value 0, current value 1
Number of orders in the history has been changed. Previous value 0, current value 1
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
另见 OrderSend、OrderSendAsync、OnTradeTransaction、客户端事件
# 13.8 OnTradeTransaction
当TradeTransaction事件发生时在EA中调用这个函数。这个函数旨在处理交易请求执行结果。
void OnTradeTransaction()
const MqlTradeTransaction& trans, // 交易事务结构
const MqlTradeRequest& request, // 请求结构
const MqlTradeResult& result // 回应结构
);
2
3
4
5
参数 trans
[in] MqlTradeTransaction类型变量描述了在交易账户上进行的交易。
request
[in] MqlTradeRequest类型变量描述了导致交易的交易请求。它只包含TRADE_TRANSACTION_REQUEST类型交易的值。
result
[in] MqlTradeResult类型变量描述了导致交易的交易请求的执行结果。它只包含TRADE_TRANSACTION_REQUEST类型交易的值。
返回值 无返回值
注意 调用OnTradeTransaction()来处理在下列情况下由交易服务器发送至程序端的TradeTransaction事件:
使用OrderSend()/OrderSendAsync()函数,从MQL5程序发送交易请求及其后续执行; 通过GUI手动发送交易请求及其后续执行; 激活服务器上的挂单和止损单; 在交易服务器端执行操作。
关于交易类型的数据包含在trans变量的类型字段中。交易事务类型在ENUM_TRADE_TRANSACTION_TYPE枚举中描述: TRADE_TRANSACTION_ORDER_ADD – 添加一个新的活跃订单 TRADE_TRANSACTION_ORDER_UPDATE – 更改现有订单 TRADE_TRANSACTION_ORDER_DELETE – 从活跃订单列表删除一个订单 TRADE_TRANSACTION_DEAL_ADD – 在历史中添加一笔交易 TRADE_TRANSACTION_DEAL_UPDATE – 在历史中改变一笔交易 TRADE_TRANSACTION_DEAL_DELETE – 从历史删除一笔交易 TRADE_TRANSACTION_HISTORY_ADD – 在历史中添加一个订单作为执行或取消的结果 TRADE_TRANSACTION_HISTORY_UPDATE – 在订单历史中更改一个订单 TRADE_TRANSACTION_HISTORY_DELETE – 从订单历史中删除一个订单 TRADE_TRANSACTION_POSITION – 与交易执行无关的持仓变化 TRADE_TRANSACTION_REQUEST – 已收到服务器处理交易请求的通知和处理结果。 当处理TRADE_TRANSACTION_REQUEST类型交易时,需要分析OnTradeTransaction()函数的第二和第三个参数 – request和result – 来获取额外信息。
发送买入交易请求会导致交易账户上的一系列交易:1) 请求被接受进行处理,2) 为该账户创建了一个适当的购买订单,3) 然后执行订单,4) 已执行订单从活跃订单列表中移除,5) 添加到订单历史中,6) 随后事务被添加到历史中以及 7) 创建新持仓。所有这些阶段都是交易事务过程。每一个这类交易事务到达程序端就是TradeTransaction事件。不保证这些交易到达程序端的优先级。因此,在开发您的EA交易时,您不应该期望一组交易将在另一组之后到达。
当交易由EA的OnTradeTransaction()处理程序进行处理时,程序端将继续处理传入的交易事务。因此,在OnTradeTransaction()操作过程中,交易账户状态可能会发生变化。例如,当MQL5程序处理添加新订单时,它可以被执行,被从已开设订单的列表中删除以及被移动到历史中。所有这些事件的消息都将通知该程序。
交易事务队列长度由1024个要素组成。如果OnTradeTransaction()处理另一个事务时间太长,那么之前的事务就可能被队列中的新事物所取代。
适当调用OnTradeTransaction()之后调用OnTrade()处理程序。通常,在OnTrade ()和OnTradeTransaction ()调用的数量上没有确切的相关性。OnTrade()一次调用对应一次或多次的OnTradeTransaction调用。
每一个Trade事件都可能作为一个或多个交易请求的结果出现。交易请求使用OrderSend()或OrderSendAsync(),被发送到服务器。每个请求都可能导致多个交易事件。您不能依赖“一个请求,一个交易事件”的声明,因为事件的处理可能分为几个阶段执行,并且每个操作都可能更改订单的状态、持仓和交易历史记录。
EA样例,OnTradeTransaction()处理程序
//+------------------------------------------------------------------+
//| OnTradeTransaction_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample listener of TradeTransaction events"
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//---
PrintFormat("LAST PING=%.f ms",
TerminalInfoInteger(TERMINAL_PING_LAST)/1000.);
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| TradeTransaction函数 |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
//---
static int counter=0; // counter of OnTradeTransaction() calls
static uint lasttime=0; // time of the OnTradeTransaction() last call
//---
uint time=GetTickCount();
//--- 如果最后一个交易事务在1秒之前执行,
if(time-lasttime>1000)
{
counter=0; // 那么这是一个新交易操作,计数器可被重置
if(IS_DEBUG_MODE)
Print(" New trade operation");
}
lasttime=time;
counter++;
Print(counter,". ",__FUNCTION__);
//--- 交易请求执行的结果
ulong lastOrderID =trans.order;
ENUM_ORDER_TYPE lastOrderType =trans.order_type;
ENUM_ORDER_STATE lastOrderState=trans.order_state;
//--- 执行交易的交易品种名称
string trans_symbol=trans.symbol;
//--- 交易事务类型
ENUM_TRADE_TRANSACTION_TYPE trans_type=trans.type;
switch(trans.type)
{
case TRADE_TRANSACTION_POSITION: //持仓变化
{
ulong pos_ID=trans.position;
PrintFormat("MqlTradeTransaction: Position #%d %s modified: SL=%.5f TP=%.5f",
pos_ID,trans_symbol,trans.price_sl,trans.price_tp);
}
break;
case TRADE_TRANSACTION_REQUEST: // sending a trade request
PrintFormat("MqlTradeTransaction: TRADE_TRANSACTION_REQUEST");
break;
case TRADE_TRANSACTION_DEAL_ADD: // 添加一个交易
{
ulong lastDealID =trans.deal;
ENUM_DEAL_TYPE lastDealType =trans.deal_type;
double lastDealVolume=trans.volume;
//--- 内部系统中的交易ID(Trade ID) - 由交易所分配的单号
string Exchange_ticket="";
if(HistoryDealSelect(lastDealID))
Exchange_ticket=HistoryDealGetString(lastDealID,DEAL_EXTERNAL_ID);
if(Exchange_ticket!="")
Exchange_ticket=StringFormat("(Exchange deal=%s)",Exchange_ticket);
PrintFormat("MqlTradeTransaction: %s deal #%d %s %s %.2f lot %s",EnumToString(trans_type),
lastDealID,EnumToString(lastDealType),trans_symbol,lastDealVolume,Exchange_ticket);
}
break;
case TRADE_TRANSACTION_HISTORY_ADD: // 在历史中添加一个订单
{
//--- 内部系统中的订单ID - 由交易所分配的单号
string Exchange_ticket="";
if(lastOrderState==ORDER_STATE_FILLED)
{
if(HistoryOrderSelect(lastOrderID))
Exchange_ticket=HistoryOrderGetString(lastOrderID,ORDER_EXTERNAL_ID);
if(Exchange_ticket!="")
Exchange_ticket=StringFormat("(Exchange ticket=%s)",Exchange_ticket);
}
PrintFormat("MqlTradeTransaction: %s order #%d %s %s %s %s",EnumToString(trans_type),
lastOrderID,EnumToString(lastOrderType),trans_symbol,EnumToString(lastOrderState),Exchange_ticket);
}
break;
default: // 其他交易事务
{
//--- 内部系统中的订单ID - 由莫斯科交易所(Moscow Exchange)分配的单号
string Exchange_ticket="";
if(lastOrderState==ORDER_STATE_PLACED)
{
if(OrderSelect(lastOrderID))
Exchange_ticket=OrderGetString(ORDER_EXTERNAL_ID);
if(Exchange_ticket!="")
Exchange_ticket=StringFormat("Exchange ticket=%s",Exchange_ticket);
}
PrintFormat("MqlTradeTransaction: %s order #%d %s %s %s",EnumToString(trans_type),
lastOrderID,EnumToString(lastOrderType),EnumToString(lastOrderState),Exchange_ticket);
}
break;
}
//--- 订单单号
ulong orderID_result=result.order;
string retcode_result=GetRetcodeID(result.retcode);
if(orderID_result!=0)
PrintFormat("MqlTradeResult: order #%d retcode=%s ",orderID_result,retcode_result);
//---
}
//+------------------------------------------------------------------+
//| 将数字响应代码转换为字符串记忆 |
//+------------------------------------------------------------------+
string GetRetcodeID(int retcode)
{
switch(retcode)
{
case 10004: return("TRADE_RETCODE_REQUOTE"); break;
case 10006: return("TRADE_RETCODE_REJECT"); break;
case 10007: return("TRADE_RETCODE_CANCEL"); break;
case 10008: return("TRADE_RETCODE_PLACED"); break;
case 10009: return("TRADE_RETCODE_DONE"); break;
case 10010: return("TRADE_RETCODE_DONE_PARTIAL"); break;
case 10011: return("TRADE_RETCODE_ERROR"); break;
case 10012: return("TRADE_RETCODE_TIMEOUT"); break;
case 10013: return("TRADE_RETCODE_INVALID"); break;
case 10014: return("TRADE_RETCODE_INVALID_VOLUME"); break;
case 10015: return("TRADE_RETCODE_INVALID_PRICE"); break;
case 10016: return("TRADE_RETCODE_INVALID_STOPS"); break;
case 10017: return("TRADE_RETCODE_TRADE_DISABLED"); break;
case 10018: return("TRADE_RETCODE_MARKET_CLOSED"); break;
case 10019: return("TRADE_RETCODE_NO_MONEY"); break;
case 10020: return("TRADE_RETCODE_PRICE_CHANGED"); break;
case 10021: return("TRADE_RETCODE_PRICE_OFF"); break;
case 10022: return("TRADE_RETCODE_INVALID_EXPIRATION"); break;
case 10023: return("TRADE_RETCODE_ORDER_CHANGED"); break;
case 10024: return("TRADE_RETCODE_TOO_MANY_REQUESTS"); break;
case 10025: return("TRADE_RETCODE_NO_CHANGES"); break;
case 10026: return("TRADE_RETCODE_SERVER_DISABLES_AT"); break;
case 10027: return("TRADE_RETCODE_CLIENT_DISABLES_AT"); break;
case 10028: return("TRADE_RETCODE_LOCKED"); break;
case 10029: return("TRADE_RETCODE_FROZEN"); break;
case 10030: return("TRADE_RETCODE_INVALID_FILL"); break;
case 10031: return("TRADE_RETCODE_CONNECTION"); break;
case 10032: return("TRADE_RETCODE_ONLY_REAL"); break;
case 10033: return("TRADE_RETCODE_LIMIT_ORDERS"); break;
case 10034: return("TRADE_RETCODE_LIMIT_VOLUME"); break;
case 10035: return("TRADE_RETCODE_INVALID_ORDER"); break;
case 10036: return("TRADE_RETCODE_POSITION_CLOSED"); break;
default:
return("TRADE_RETCODE_UNKNOWN="+IntegerToString(retcode));
break;
}
//---
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
另见
OrderSend、OrderSendAsync、OnTradeTransaction、交易请求结构、交易事务结构、交易事务类型、交易操作类型、 客户端事件
# 13.9 OnBookEvent
当BookEvent事件发生时在指标和EA中调用这个函数。它是用来处理市场深度的变化。
void OnBookEvent(
const string& symbol //交易品种
);
2
3
参数 交易品种
[in] BookEvent到达的交易品种名称
返回值 无返回值
注意 若要获得任何交易品种的BookEvent事件,只需使用MarketBookAdd()函数,为该交易品种订阅接收该事件。若要为特定交易品种取消订阅接收BookEvent,则调用MarketBookRelease()函数。
BookEvent在整个图表中进行传播。这意味着,如果图表上的一个应用程序使用MarketBookAdd函数订阅BookEvent,那么在相同图表上启用的所有其他指标和EA以及OnBookEvent()处理程序也会收到这个事件。因此,有必要分析传递到OnBookEvent()处理程序的交易品种名称,并将其作为交易品种参数。
为运行在相同图表上的所有应用程序提供按交易品种分类的单独的BookEvent计数器。这意味着每个图表对不同的交易品种可能有多个订阅,并且为每个交易品种提供一个计数器。从BookEvent订阅和取消订阅,只在一个图表中更改指定交易品种的订阅计数器。换句话说,对于相同的交易品种,BookEvent可能有两个相邻的图表,但订阅计数值不同。
初始的订阅计数值为零。每调用一次MarketBookAdd(),图表上特定交易品种的订阅计数器都会增加1个(图表交易品种和MarketBookAdd()中的交易品种不必匹配)。当调用MarketBookRelease()时,在图表中指定交易品种的订阅计数器就会减少1个。任何交易品种的BookEvent事件都在图表中广播,直至计数器的值等于零。因此,很重要的一点就是,每个包含了MarketBookAdd ()调用的MQL5程序都在其工作结束时使用MarketBookRelease ()为每个交易品种正确取消订阅获取事件。为此,MarketBookAdd()和MarketBookRelease()的调用数量甚至都应该适用于整个MQL5程序生命周期内的每个调用。在程序中使用标识或自定义订阅计数器可以使您安全地处理BookEvent事件,并阻止禁用订阅,以在同一图表内的第三方程序中获取该事件。
即使处理之前的BookEvent处理还没有结束,BookEvent 事件也不会被跳过,会始终被置于队列之中。
例如
//+------------------------------------------------------------------+
//| OnBookEvent_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com/en/articles/2635"
#property version "1.00"
#property description "Example of measuring the market depth refresh rate using OnBookEvent()"
#property description "The code is taken from the article https://www.mql5.com/en/articles/2635"
//--- 输入参数
input ulong ExtCollectTime =30; // test time in seconds
input ulong ExtSkipFirstTicks=10; // number of ticks skipped at start
//--- 订阅BookEvent事件的标识
bool book_subscribed=false;
//--- 接受来自市场深度的请求的数组
MqlBookInfo book[];
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 显示开始
Comment(StringFormat("Waiting for the first %I64u ticks to arrive",ExtSkipFirstTicks));
PrintFormat("Waiting for the first %I64u ticks to arrive",ExtSkipFirstTicks);
//--- 启用市场深度广播
if(MarketBookAdd(_Symbol))
{
book_subscribed=true;
PrintFormat("%s: MarketBookAdd(%s) function returned true",__FUNCTION__,_Symbol);
}
else
PrintFormat("%s: MarketBookAdd(%s) function returned false! GetLastError()=%d",__FUNCTION__,_Symbol,GetLastError());
//--- 成功初始化
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 去初始化EA交易 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 显示去初始化原因代码
Print(__FUNCTION__,": Deinitialization reason code = ",reason);
//--- 取消订阅以获取市场深度事件
if(book_subscribed)
{
if(!MarketBookRelease(_Symbol))
PrintFormat("%s: MarketBookRelease(%s) returned false! GetLastError()=%d",_Symbol,GetLastError());
else
book_subscribed=false;
}
//---
}
//+------------------------------------------------------------------+
//| BookEvent函数 |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
static ulong starttime=0; // 测试开始时间
static ulong tickcounter=0; // 市场深度更新计数器
//--- 只有当我们自己订阅市场深度事件时处理它们
if(!book_subscribed)
return;
//--- 只为特定交易品种计算更新
if(symbol!=_Symbol)
return;
//--- 跳过第一个报价来清空队列和进行准备
tickcounter++;
if(tickcounter<ExtSkipFirstTicks)
return;
//--- 记住开始时间
if(tickcounter==ExtSkipFirstTicks)
starttime=GetMicrosecondCount();
//--- 请求市场深度数据
MarketBookGet(symbol,book);
//--- 什么时候停止?
ulong endtime=GetMicrosecondCount()-starttime;
ulong ticks =1+tickcounter-ExtSkipFirstTicks;
// 从测试开始以后,已经过去多少毫秒?
if(endtime>ExtCollectTime*1000*1000)
{
PrintFormat("%I64u ticks for %.1f seconds: %.1f ticks/sec ",ticks,endtime/1000.0/1000.0,ticks*1000.0*1000.0/endtime);
ExpertRemove();
return;
}
//--- 在评论字段中显示计数器
if(endtime>0)
Comment(StringFormat("%I64u ticks for %.1f seconds: %.1f ticks/sec ",ticks,endtime/1000.0/1000.0,ticks*1000.0*1000.0/endtime));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
另见 MarketBookAdd、MarketBookRelease、MarketBookGet、 OnTrade、OnTradeTransaction、OnTick、 事件处理函数、程序运行、客户端事件
# 13.10 OnChartEvent
当ChartEvent事件发生时在指标和EA中调用这个函数。这个函数意在处理由用户或者MQL5程序所做的图表更改。
void OnChartEvent()
const int id, // 事件ID
const long& lparam, // 长整型(long)事件参数
const double& dparam, // 长整型(double)事件参数
const string& sparam // 字符串型(string)事件参数
);
2
3
4
5
6
参数 id
[in] 来自ENUM_CHART_EVENT枚举的事件ID。
lparam
[in] 长整型(long)事件参数
dparam
[in] 双精度型(double)事件参数
sparam
[in] 字符串型(string)事件参数
返回值 无返回值
注意 共有11种事件类型可以使用预定义OnChartEvent()函数来处理。为自定义事件提供了从CHARTEVENT_CUSTOM到CHARTEVENT_CUSTOM_LAST包含的65535个ID。若要生成自定义事件,请使用EventChartCustom()函数。
来自ENUM_CHART_EVENT 枚举的简短事件描述:
CHARTEVENT_KEYDOWN ― 当图表窗口处于焦点时,按下键盘上的一个按键;
CHARTEVENT_MOUSE_MOVE ― 移动鼠标,并且鼠标按键点击(如果图表 CHART_EVENT_MOUSE_MOVE=true); CHARTEVENT_OBJECT_CREATE ― 创建一个图形对象(如果图表 CHART_EVENT_OBJECT_CREATE=true); CHARTEVENT_OBJECT_CHANGE ― 通过属性对话框更改对象属性; CHARTEVENT_OBJECT_DELETE ― 删除一个图形对象(如果图表 CHART_EVENT_OBJECT_DELETE=true); CHARTEVENT_CLICK ― 点击图表; CHARTEVENT_OBJECT_CLICK ― 鼠标单击属于图表的图形对象; CHARTEVENT_OBJECT_DRAG ― 用鼠标拖动图形对象; CHARTEVENT_OBJECT_ENDEDIT ― 在图形对象的“编辑”输入框中完成文本编辑(OBJ_EDIT); CHARTEVENT_CHART_CHANGE ― 更改图表; CHARTEVENT_CUSTOM+n ― 自定义事件ID,这里n的范围是从0到65535。CHARTEVENT_CUSTOM_LAST包含最后可接受的自定义事件ID (CHARTEVENT_CUSTOM+65535)。
所有MQL5程序都使用了不同于应用程序主线程的其他线程。主应用程序线程负责处理所有的Windows系统消息,反过来,作为该处理的结果,为其自身的应用程序生成Windows消息。例如,在图表上移动鼠标(WM_MOUSE_MOVE事件)为随后呈现的应用程序窗口生成几个系统消息,并将内部消息发送到在图表上启动的EA和指标。可能会出现这样的情况,主要的应用程序线程还没有处理WM_PAINT系统消息(因此还没有呈现修改过的图表),而EA或指标却已经收到了鼠标移动事件。在这种情况下,只有在呈现图表之后,图表属性CHART_FIRST_VISIBLE_BAR才会更改。
对于每种事件类型,OnChartEvent()函数的输入都具有处理该事件所需的某些值。该表列出了通过参数传递的事件和值。
事件 | 'id' 参数值 | 'lparam'参数值 | 'dparam'参数值 | 'sparam'参数值 |
---|---|---|---|---|
Keystroke事件 | CHARTEVENT_KEYDOWN | 按下按键代码 | 在按键处于按下状态时生成的按键响应的数量 | 位掩码的字符串值,描述键盘按键的状态 |
鼠标事件 (如果图表CHART_EVENT_MOUSE_MOVE=true) | CHARTEVENT_MOUSE_MOVE | X 坐标 | Y 坐标 | 位掩码的字符串值,描述鼠标按键的状态 |
鼠标滚轮事件(如果CHART_EVENT_MOUSE_WHEEL=true为图表设置) | CHARTEVENT_MOUSE_WHEEL | 键盘按键和鼠标按键的状态标识,光标的X和Y坐标。请参阅示例中的描述 | 鼠标滚轮滚动的Delta值 | ― |
创建一个图形对象(如果图表CHART_EVENT_OBJECT_CREATE=true) | CHARTEVENT_OBJECT_CREATE | ― | ― | 已创建图形对象的名称 |
通过属性对话框更改对象属性 | CHARTEVENT_OBJECT_CHANGE | ― | ― | 已更改图形对象的名称 |
移除一个图形对象(如果图表CHART_EVENT_OBJECT_DELETE=true) | CHARTEVENT_OBJECT_DELETE | ― | ― | 已移除图形对象的名称 |
图表点击图表 | CHARTEVENT_CLICK | X 坐标 | Y 坐标 | ― |
鼠标点击图形对象 | CHARTEVENT_OBJECT_CLICK | X 坐标 | Y 坐标 | 事件所发生的图形对象的名称 |
使用鼠标移动图形对象 | CHARTEVENT_OBJECT_DRAG | ― | ― | 已移动图形对象的名称 |
在图形对象输入框的“输入字段”中完成文本编辑 | CHARTEVENT_OBJECT_ENDEDIT | ― | ― | “输入字段”图形对象的名称,在这里完成文本编辑 |
通过属性对话框窗口,调整图表大小或更改图表属性 | CHARTEVENT_CHART_CHANGE | ― | ― | ― |
N个数字的自定义事件 | CHARTEVENT_CUSTOM+N | 通过EventChartCustom()函数定义的值 | 通过EventChartCustom()函数定义的值 | 通过EventChartCustom()函数定义的值 |
示例图表事件监听器:
//+------------------------------------------------------------------+
//| OnChartEvent_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample chart event listener and custom events generator"
//--- 服务键ID
#define KEY_NUMPAD_5 12
#define KEY_LEFT 37
#define KEY_UP 38
#define KEY_RIGHT 39
#define KEY_DOWN 40
#define KEY_NUMLOCK_DOWN 98
#define KEY_NUMLOCK_LEFT 100
#define KEY_NUMLOCK_5 101
#define KEY_NUMLOCK_RIGHT 102
#define KEY_NUMLOCK_UP 104
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 显示CHARTEVENT_CUSTOM常数值
Print("CHARTEVENT_CUSTOM=",CHARTEVENT_CUSTOM);
//---
Print("Launched the EA ",MQLInfoString(MQL5_PROGRAM_NAME));
//--- 设置接收图表对象创建事件的标识
ChartSetInteger(ChartID(),CHART_EVENT_OBJECT_CREATE,true);
//--- 设置接收图表对象移除事件的标识
ChartSetInteger(ChartID(),CHART_EVENT_OBJECT_DELETE,true);
//--- 启用鼠标滚轮来滚动信息
ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1);
//--- 强制更新图表属性确保做好事件处理的准备
ChartRedraw();
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 用于生成自定义事件的报价计数器
static int tick_counter=0;
//--- 通过这个值除以累积的报价
int simple_number=113;
//---
tick_counter++;
//--- 如果报价计数器是simple_number的倍数,则发送自定义事件
if(tick_counter%simple_number==0)
{
//--- 形成自定义事件ID(从0到65535)
ushort custom_event_id=ushort(tick_counter%65535);
//--- 发送参数填充的自定义事件
EventChartCustom(ChartID(),custom_event_id,tick_counter,SymbolInfoDouble(Symbol(),SYMBOL_BID),__FUNCTION__);
//--- 添加到日志来分析示例结果
Print(__FUNCTION__,": Sent a custom event ID=",custom_event_id);
}
//---
}
//+------------------------------------------------------------------+
//| ChartEvent 函数 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
//--- 按键响应
if(id==CHARTEVENT_KEYDOWN)
{
switch((int)lparam)
{
case KEY_NUMLOCK_LEFT: Print("Pressed KEY_NUMLOCK_LEFT"); break;
case KEY_LEFT: Print("Pressed KEY_LEFT"); break;
case KEY_NUMLOCK_UP: Print("Pressed KEY_NUMLOCK_UP"); break;
case KEY_UP: Print("Pressed KEY_UP"); break;
case KEY_NUMLOCK_RIGHT: Print("Pressed KEY_NUMLOCK_RIGHT"); break;
case KEY_RIGHT: Print("Pressed KEY_RIGHT"); break;
case KEY_NUMLOCK_DOWN: Print("Pressed KEY_NUMLOCK_DOWN"); break;
case KEY_DOWN: Print("Pressed KEY_DOWN"); break;
case KEY_NUMPAD_5: Print("Pressed KEY_NUMPAD_5"); break;
case KEY_NUMLOCK_5: Print("Pressed KEY_NUMLOCK_5"); break;
default: Print("Pressed unlisted key");
}
}
//--- 左击图表
if(id==CHARTEVENT_CLICK)
Print("Mouse click coordinates on a chart: x = ",lparam," y = ",dparam);
//--- 点击图形对象
if(id==CHARTEVENT_OBJECT_CLICK)
Print("Clicking a mouse button on an object named '"+sparam+"'");
//--- 被移除的对象
if(id==CHARTEVENT_OBJECT_DELETE)
Print("Removed object named ",sparam);
//--- 被创建的对象
if(id==CHARTEVENT_OBJECT_CREATE)
Print("Created object named ",sparam);
//--- 被更改的对象
if(id==CHARTEVENT_OBJECT_CHANGE)
Print("Changed object named ",sparam);
//--- 被移动的对象或被改变的定位点坐标
if(id==CHARTEVENT_OBJECT_DRAG)
Print("Changing anchor points of object named ",sparam);
//--- 在“编辑”图形对象的输入字段更改文本
if(id==CHARTEVENT_OBJECT_ENDEDIT)
Print("Changed text in Edit object ",sparam," id=",id);
//--- 鼠标移动事件
if(id==CHARTEVENT_MOUSE_MOVE)
Comment("POINT: ",(int)lparam,",",(int)dparam,"\n",MouseState((uint)sparam));
if(id==CHARTEVENT_MOUSE_WHEEL)
{
//--- 考虑这个事件的鼠标按键和滚轮的状态
int flg_keys = (int)(lparam>>32); // Ctrl 和 Shift 键,鼠标按键的状态标识
int x_cursor = (int)(short)lparam; // 鼠标滚轮事件发生的X坐标
int y_cursor = (int)(short)(lparam>>16); // 鼠标滚轮事件发生的Y坐标
int delta = (int)dparam; // 鼠标滚动的总值,到达 +120 或 -120 时触发
//--- 处理标识
string str_keys="";
if((flg_keys&0x0001)!=0)
str_keys+="LMOUSE ";
if((flg_keys&0x0002)!=0)
str_keys+="RMOUSE ";
if((flg_keys&0x0004)!=0)
str_keys+="SHIFT ";
if((flg_keys&0x0008)!=0)
str_keys+="CTRL ";
if((flg_keys&0x0010)!=0)
str_keys+="MMOUSE ";
if((flg_keys&0x0020)!=0)
str_keys+="X1MOUSE ";
if((flg_keys&0x0040)!=0)
str_keys+="X2MOUSE ";
if(str_keys!="")
str_keys=", keys='"+StringSubstr(str_keys,0,StringLen(str_keys)-1)+"'";
PrintFormat("%s: X=%d, Y=%d, delta=%d%s",EnumToString(CHARTEVENT_MOUSE_WHEEL),x_cursor,y_cursor,delta,str_keys);
}
//--- 使用属性对话框窗口调整图表大小或更改图表属性的事件
if(id==CHARTEVENT_CHART_CHANGE)
Print("Changing the chart size or properties");
//--- 自定义事件
if(id>CHARTEVENT_CUSTOM)
PrintFormat("Custom event ID=%d, lparam=%d, dparam=%G, sparam=%s",id,lparam,dparam,sparam);
}
//+------------------------------------------------------------------+
//| 鼠标点选位置 |
//+------------------------------------------------------------------+
string MouseState(uint state)
{
string res;
res+="\nML: " +(((state& 1)== 1)?"DN":"UP"); // 鼠标左移
res+="\nMR: " +(((state& 2)== 2)?"DN":"UP"); // 鼠标右移
res+="\nMM: " +(((state&16)==16)?"DN":"UP"); // 鼠标居中
res+="\nMX: " +(((state&32)==32)?"DN":"UP"); // 鼠标点选第一个X键
res+="\nMY: " +(((state&64)==64)?"DN":"UP"); // 鼠标点选第二个 X 键
res+="\nSHIFT: "+(((state& 4)== 4)?"DN":"UP"); // 移位键
res+="\nCTRL: " +(((state& 8)== 8)?"DN":"UP"); // 控制键
return(res);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
另见 EventChartCustom、图表事件类型、事件处理函数、程序运行、客户端事件
# 13.11 OnTester
当Tester事件发生时在EA中调用这个函数,测试之后执行必要的操作。
double OnTester(void);
返回值 用于评估测试结果的自定义标准优化的值。
注意 OnTester()函数只有在测试EA时可被使用,主要用于计算在优化输入参数时用作‘Custom max(最大自定义)’准则的值。
在遗传优化过程中,在一代内对结果进行降序排列。这意味着从优化准则的角度来看,具有最高价值的结果被认为是最高的。这种排序最糟糕的值被排在最后,并随后丢弃。因此,它们不参与形成下一代。
因此,OnTester()函数不仅允许您创建和保存您自己的测试结果报告,还可以控制优化过程,找出交易策略的最佳参数。
以下就是计算自定义准则优化的示例。意在计算结余图形的线性回归。这在使用结余图形优化策略,并将结果与“结余 + 最大夏普比率”准则进行比较文章中进行描述。
//+------------------------------------------------------------------+
//| OnTester_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample EA with the OnTester() handler"
#property description "As a custom optimization criterion, "
#property description "the ratio of the balance graph linear regression"
#property description "divided by the deviation mean-square error is returned"
//--- 包含交易操作类
#include <Trade\Trade.mqh>
//--- EA输入参数
input double Lots = 0.1; // 交易量
input int Slippage = 10; // 可允许滑移
input int MovingPeriod = 80; // 移动平均线时间
input int MovingShift = 6; // 移动平均线移位
//--- 全局变量
int IndicatorHandle=0; // 指标句柄
bool IsHedging=false; // 账户标识
CTrade trade; // 为执行交易操作
//---
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| 检查持仓开仓条件 |
//+------------------------------------------------------------------+
void CheckForOpen(void)
{
MqlRates rt[2];
//--- 仅在新柱形图开始进行交易
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
//--- 报价量
if(rt[1].tick_volume>1)
return;
//--- 接收移动平均线值
double ma[1];
if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
{
Print("CopyBuffer from iMA failed, no data");
return;
}
//--- 检查信号是否存在
ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- 蜡烛图开盘高于移动平均线但收盘低于移动平均线
if(rt[0].open > ma[0] && rt[0].close < ma[0])
signal=ORDER_TYPE_BUY; //买入信号
else // 蜡烛图开盘低于移动平均线但收盘高于移动平均线
{
if(rt[0].open < ma[0] && rt[0].close > ma[0])
signal=ORDER_TYPE_SELL;// 卖出信号
}
//--- 附加检查
if(signal!=WRONG_VALUE)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
{
double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);
trade.PositionOpen(_Symbol,signal,Lots,price,0,0);
}
}
//---
}
//+------------------------------------------------------------------+
//| 检查持仓平仓条件 |
//+------------------------------------------------------------------+
void CheckForClose(void)
{
MqlRates rt[2];
//--- 仅在新柱形图开始进行交易
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
if(rt[1].tick_volume>1)
return;
//--- 接收移动平均线值
double ma[1];
if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
{
Print("CopyBuffer from iMA failed, no data");
return;
}
//--- 已使用PositionSelect()提前选择持仓
bool signal=false;
long type=PositionGetInteger(POSITION_TYPE);
//--- 蜡烛图开盘高于移动平均线但收盘低于移动平均线――关闭卖出持仓
if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close < ma[0])
signal=true;
//--- 蜡烛图开盘低于移动平均线但收盘高于移动平均线――关闭买入持仓
if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
signal=true;
//--- 附加检查
if(signal)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
trade.PositionClose(_Symbol,Slippage);
}
//---
}
//+-------------------------------------------------------------------+
//| 选择一个考虑账户类型的持仓:单边持仓或锁仓持仓 |
//+-------------------------------------------------------------------+
bool SelectPosition()
{
bool res=false;
//--- 选择一个锁仓账户系统的持仓
if(IsHedging)
{
uint total=PositionsTotal();
for(uint i=0; i<total; i++)
{
string position_symbol=PositionGetSymbol(i);
if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))
{
res=true;
break;
}
}
}
//--- 选择一个单边账户系统的持仓
else
{
if(!PositionSelect(_Symbol))
return(false);
else
return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---check Magic number
}
//--- 执行结果
return(res);
}
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit(void)
{
//--- 设置一个交易类型:单边交易类型或锁仓交易类型
IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- 初始化一个用于正确持仓控制的对象
trade.SetExpertMagicNumber(EA_MAGIC);
trade.SetMarginMode();
trade.SetTypeFillingBySymbol(Symbol());
trade.SetDeviationInPoints(Slippage);
//--- 创建移动平均线指标
IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
if(IndicatorHandle==INVALID_HANDLE)
{
printf("Error creating iMA indicator");
return(INIT_FAILED);
}
//--- ok
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick(void)
{
//--- 如果已开仓,请检查平仓条件
if(SelectPosition())
CheckForClose();
// 检查持仓开仓条件
CheckForOpen();
//---
}
//+------------------------------------------------------------------+
//| 测试函数 |
//+------------------------------------------------------------------+
double OnTester()
{
//--- 自定义准则优化值(越高越好)
double ret=0.0;
//--- 将交易结果转到数组
double array[];
double trades_volume;
GetTradeResultsToArray(array,trades_volume);
int trades=ArraySize(array);
//--- 如果少于10个交易,那么测试结果没有正值
if(trades < 10)
return (0);
//--- 每个交易的平均结果
double average_pl=0;
for(int i=0;i<ArraySize(array);i++)
average_pl+=array[i];
average_pl/=trades;
//--- 显示单测试模式的消息
if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
PrintFormat("%s: Trades=%d, Average profit=%.2f",__FUNCTION__,trades,average_pl);
//--- 计算利润图的线性回归比
double a,b,std_error;
double chart[];
if(!CalculateLinearRegression(array,chart,a,b))
return (0);
//--- 计算回归线与图表偏差的误差
if(!CalculateStdError(chart,a,b,std_error))
return (0);
//--- 计算趋势利润与标准偏差的比率
ret=a*trades/std_error;
//--- 返回自定义标准优化值
return(ret);
}
//+------------------------------------------------------------------+
//| 获取交易的利润/亏损的数组 |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
{
//--- 请求完整的交易历史
if(!HistorySelect(0,TimeCurrent()))
return (false);
uint total_deals=HistoryDealsTotal();
volume=0;
//--- 设置预付款数组的初始大小 - 根据历史中的交易数量
ArrayResize(pl_results,total_deals);
//--- 解决交易结果的交易计数器 - 盈利或亏损
int counter=0;
ulong ticket_history_deal=0;
//--- 检查所有交易
for(uint i=0;i<total_deals;i++)
{
//--- 选择一个交易
if((ticket_history_deal=HistoryDealGetTicket(i))>0)
{
ENUM_DEAL_ENTRY deal_entry =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);
long deal_type =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);
double deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);
double deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);
//--- 我们只对交易操作感兴趣
if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
continue;
//--- 只有解决盈利/亏损的交易
if(deal_entry!=DEAL_ENTRY_IN)
{
//--- 将交易结果写入数组并增加交易的计数器
pl_results[counter]=deal_profit;
volume+=deal_volume;
counter++;
}
}
}
//--- 设置数组的最终大小
ArrayResize(pl_results,counter);
return (true);
}
//+------------------------------------------------------------------+
//| 计算线性回归 y=a*x+b |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double &change[],double &chartline[],
double &a_coef,double &b_coef)
{
//--- 检查数据充分性
if(ArraySize(change)<3)
return (false);
//--- 创建一个累积图表数组
int N=ArraySize(change);
ArrayResize(chartline,N);
chartline[0]=change[0];
for(int i=1;i<N;i++)
chartline[i]=chartline[i-1]+change[i];
//--- 现在,计算回归比率
double x=0,y=0,x2=0,xy=0;
for(int i=0;i<N;i++)
{
x=x+i;
y=y+chartline[i];
xy=xy+i*chartline[i];
x2=x2+i*i;
}
a_coef=(N*xy-x*y)/(N*x2-x*x);
b_coef=(y-a_coef*x)/N;
//---
return (true);
}
//+------------------------------------------------------------------+
//| 计算指定a和b的平均-平方偏差误差 |
//+------------------------------------------------------------------+
bool CalculateStdError(double &data[],double a_coef,double b_coef,double &std_err)
{
//--- 平方和错误
double error=0;
int N=ArraySize(data);
if(N==0)
return (false);
for(int i=0;i<N;i++)
error=MathPow(a_coef*i+b_coef-data[i],2);
std_err=MathSqrt(error/(N-2));
//---
return (true);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
另见 测试交易策略、TesterHideIndicators、处理优化结果、 TesterStatistics、OnTesterInit、OnTesterDeinit、 OnTesterPass、MQL_TESTER、 MQL_OPTIMIZATION、FileOpen、FileWrite、FileLoad、FileSave
# 13.12 OnTesterInit
当TesterInit事件发生时在EA中调用这个函数,以便在策略测试优化之前执行必要的操作。有两种函数类型。
返回结果的版本
int OnTesterInit(void);
返回值
int 类型值,0意味着优化开始之前图表上启动的EA的成功初始化。
建议使用返回执行结果的OnTesterInit()调用,因为它不仅可以程序初始化,还可以在早期优化停止的情况下返回一个错误代码。返回INIT_SUCCEEDED 以外的任何值,(0)表示错误,没有启动优化。
没有结果返回的版本只为与旧代码兼容而保留。不建议使用
void OnTesterInit(void);
注意 TesterInit事件在策略测试开始期间EA优化之前生成。在该事件中,带有OnTesterDeInit()或OnTesterPass()事件处理程序的EA会在单独的程序端图表上自动下载。它包括测试中已经制定的交易品种和周期。
这类事件接收TesterInit、TesterDeinit和TesterPass事件,但不接收 Init、Deinit和NewTick事件。因此,优化期间处理每次传递结果的所有必要逻辑都应该在OnTesterInit ()、OnTesterDeinit ()和OnTesterPass ()处理程序中实施。
策略优化期间每次单独传递的结果都可以使用FrameAdd ()函数,通过来自OnTester ()处理程序的框架进行传递。
OnTesterInit()函数被用来在优化开始之前启动EA交易,以便进一步处理优化结果。它经常与OnTesterDeinit()处理程序一起使用。
OnTesterInit()的执行时间是有限的。如果超过限定时间,EA会被强制停止,而优化本身也会被取消。在测试日志中显示一条消息:
测试器 OnTesterInit工作时间过长。测试器不能被初始化。
来自OnTick的示例。添加OnTesterInit()处理程序来设置优化参数:
//+------------------------------------------------------------------+
//| OnTesterInit_Sample.mq5 |
//| Copyright 2018, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Sample EA with the OnTesterInit() handler,"
#property description "in which values and limitations of "
#property description "inputs during optimization are set"
input double lots=0. 1; // 交易量手数
input double kATR=3; // ATR中的信号蜡烛长度
input int ATRperiod=20; // ATR指标周期
input int holdbars=8; // 保持持仓的柱形图数量
input int slippage=10; // 可允许滑移
input bool revers=false; //反向信号?
input ulong EXPERT_MAGIC=0; // EA幻数
//--- 用于存储ATR指标句柄
int atr_handle;
//--- 在这里,我们将存储ATR最后值和蜡烛体
double last_atr,last_body;
datetime lastbar_timeopen;
double trade_lot;
//--- 请记住优化开始时间
datetime optimization_start;
//--- 用于在优化结束后在图表上显示持续时间
string report;
//+------------------------------------------------------------------+
//| TesterInit 函数 |
//+------------------------------------------------------------------+
void OnTesterInit()
{
//--- 设置优化的输入值
ParameterSetRange("lots",false,0.1,0,0,0);
ParameterSetRange("kATR",true,3.0,1.0,0.3,7.0);
ParameterSetRange("ATRperiod",true,10,15,1,30);
ParameterSetRange("holdbars",true,5,3,1,15);
ParameterSetRange("slippage",false,10,0,0,0);
ParameterSetRange("revers",true,false,false,1,true);
ParameterSetRange("EXPERT_MAGIC",false,123456,0,0,0);
Print("Initial values and optimization parameter limitations are set");
//--- 记得开始优化
optimization_start=TimeLocal();
report=StringFormat("%s: optimization launched at %s",
__FUNCTION__,TimeToString(TimeLocal(),TIME_MINUTES|TIME_SECONDS));
//--- 显示图表上和程序端日志中的消息
Print(report);
Comment(report);
//---
}
//+------------------------------------------------------------------+
//| TesterDeinit函数 |
//+------------------------------------------------------------------+
void OnTesterDeinit()
{
//--- 优化持续时间
string log_message=StringFormat("%s: optimization took %d seconds",
__FUNCTION__,TimeLocal()-optimization_start);
PrintFormat(log_message);
report=report+"\r\n"+log_message;
Comment(report);
}
//+------------------------------------------------------------------+
//| EA交易初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 初始化全局变量
last_atr=0;
last_body=0;
//--- 设置正确的交易量
double min_lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
trade_lot=lots>min_lot? lots:min_lot;
//--- 创建ATR指标句柄
atr_handle=iATR(_Symbol,_Period,ATRperiod);
if(atr_handle==INVALID_HANDLE)
{
PrintFormat("%s: failed to create iATR, error code %d",__FUNCTION__,GetLastError());
return(INIT_FAILED);
}
//--- EA成功初始化
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA报价函数 |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 交易信号
static int signal=0; // +1表示买入信号, -1 表示卖出信号
//--- 检查和关闭‘holdbars’柱形图之前开仓的旧持仓
ClosePositionsByBars(holdbars,slippage,EXPERT_MAGIC);
//--- 检查新柱形图
if(isNewBar())
{
//--- 检查信号是否存在
signal=CheckSignal();
}
//--- 如果持有单边持仓,跳过信号-等候直至关闭
if(signal!=0 && PositionsTotal() > 0 && (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_NETTING)
{
signal=0;
return; // 退出NewTick事件处理程序,并在新柱形图出现之前不要进入市场
}
//--- 对于锁仓账户,每个持仓都是单独持仓和平仓
if(signal!=0)
{
//--- 买入信号
if(signal>0)
{
PrintFormat("%s: Buy signal! Revers=%s",__FUNCTION__,string(revers));
if(Buy(trade_lot,slippage,EXPERT_MAGIC))
signal=0;
}
//--- 卖出信号
if(signal<0)
{
PrintFormat("%s: Sell signal! Revers=%s",__FUNCTION__,string(revers));
if(Sell(trade_lot,slippage,EXPERT_MAGIC))
signal=0;
}
}
//--- OnTick函数结束
}
//+------------------------------------------------------------------+
//| 检查新的交易信号 |
//+------------------------------------------------------------------+
int CheckSignal()
{
//--- 0 意味着没有信号
int res=0;
//--- 获得倒数第二个完整柱形图的ATR值(柱形图索引是2)
double atr_value[1];
if(CopyBuffer(atr_handle,0,2,1,atr_value)!=-1)
{
last_atr=atr_value[0];
//--- 获得最后关闭的柱形图的MqlRates类型数组数据
MqlRates bar[1];
if(CopyRates(_Symbol,_Period,1,1,bar)!=-1)
{
//--- 计算最后完整柱形图的柱体大小
last_body=bar[0].close-bar[0].open;
//--- 如果最后柱形图的柱体(索引为1)超过的之前ATR的值(柱形图索引为2),那么会收到一个交易信号
if(MathAbs(last_body)>kATR*last_atr)
res=last_body>0?1:-1; // 向上蜡烛图的正值
}
else
PrintFormat("%s: Failed to receive the last bar! Error",__FUNCTION__,GetLastError());
}
else
PrintFormat("%s: Failed to receive ATR indicator value! Error",__FUNCTION__,GetLastError());
//--- 如果启用了反向交易模式
res=revers?-res:res; // 在必要时反转信号(返回-1而不是1,反之亦然)
//--- 返回一个交易信号值
return (res);
}
//+------------------------------------------------------------------+
//| 当新柱形图出现时返回'true' |
//+------------------------------------------------------------------+
bool isNewBar(const bool print_log=true)
{
static datetime bartime=0; //存储当前柱形图的开盘时间
//--- 获得零柱的开盘时间
datetime currbar_time=iTime(_Symbol,_Period,0);
//--- 如果开盘时间更改,则新柱形图出现
if(bartime!=currbar_time)
{
bartime=currbar_time;
lastbar_timeopen=bartime;
//--- 在日志中显示新柱形图开盘时间的数据
if(print_log && !(MQLInfoInteger(MQL_OPTIMIZATION)||MQLInfoInteger(MQL_TESTER)))
{
//--- 显示新柱形图开盘时间的信息
PrintFormat("%s: new bar on %s %s opened at %s",__FUNCTION__,_Symbol,
StringSubstr(EnumToString(_Period),7),
TimeToString(TimeCurrent(),TIME_SECONDS));
//--- 获取关于最后报价的数据
MqlTick last_tick;
if(!SymbolInfoTick(Symbol(),last_tick))
Print("SymbolInfoTick() failed, error = ",GetLastError());
//--- 显示最后报价的时间,精确至毫秒
PrintFormat("Last tick was at %s.%03d",
TimeToString(last_tick.time,TIME_SECONDS),last_tick.time_msc%1000);
}
//--- 我们有一个新柱形图
return (true);
}
//--- 没有新柱形图
return (false);
}
//+------------------------------------------------------------------+
//| 以指定交易量和市场价买入 |
//+------------------------------------------------------------------+
bool Buy(double volume,ulong deviation=10,ulong magicnumber=0)
{
//--- 以市场价买入
return (MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber));
}
//+------------------------------------------------------------------+
//| 以指定交易量和市场价卖出 |
//+------------------------------------------------------------------+
bool Sell(double volume,ulong deviation=10,ulong magicnumber=0)
{
//--- 以市场价卖出
return (MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber));
}
//+------------------------------------------------------------------+
//| 在柱形图中根据持有时间平仓 |
//+------------------------------------------------------------------+
void ClosePositionsByBars(int holdtimebars,ulong deviation=10,ulong magicnumber=0)
{
int total=PositionsTotal(); // 持仓数量
//--- 重复持仓
for(int i=total-1; i>=0; i--)
{
//--- 持仓参数
ulong position_ticket=PositionGetTicket(i); // 持仓单号
string position_symbol=PositionGetString(POSITION_SYMBOL); //交易品种
ulong magic=PositionGetInteger(POSITION_MAGIC); // 持仓幻数
datetime position_open=(datetime)PositionGetInteger(POSITION_TIME); // 持仓开盘时间
int bars=iBarShift(_Symbol,PERIOD_CURRENT,position_open)+1; // 开仓之前有多少柱形图
//--- 如果持仓的生命周期很长,则幻数和交易品种匹配
if(bars>holdtimebars && magic==magicnumber && position_symbol==_Symbol)
{
int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // 小数位数
double volume=PositionGetDouble(POSITION_VOLUME); // 持仓交易量
ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // 持仓类型
string str_type=StringSubstr(EnumToString(type),14);
StringToLower(str_type); //纠正消息格式的小写文本案例
PrintFormat("Close position #%d %s %s %.2f",
position_ticket,position_symbol,str_type,volume);
//--- 设置一个订单类型并发送交易请求
if(type==POSITION_TYPE_BUY)
MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber,position_ticket);
else
MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber,position_ticket);
}
}
}
//+------------------------------------------------------------------+
//| 准备和发送交易请求 |
//+------------------------------------------------------------------+
bool MarketOrder(ENUM_ORDER_TYPE type,double volume,ulong slip,ulong magicnumber,ulong pos_ticket=0)
{
//--- 声明和初始化结构
MqlTradeRequest request={0};
MqlTradeResult result={0};
double price=SymbolInfoDouble(Symbol(),SYMBOL_BID);
if(type==ORDER_TYPE_BUY)
price=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
//--- 请求参数
request.action =TRADE_ACTION_DEAL; // 交易操作类型
request.position =pos_ticket; // 关闭情况下的持仓单号
request.symbol =Symbol(); // 交易品种
request.volume =volume; // 交易量
request.type =type; // 订单类型
request.price =price; // 交易价格
request.deviation=slip; // 可允许的价格偏差
request.magic =magicnumber; // 订单幻数
//--- 发送请求
if(!OrderSend(request,result))
{
//--- 显示数据失败
PrintFormat("OrderSend %s %s %.2f at %.5f error %d",
request.symbol,EnumToString(type),volume,request.price,GetLastError());
return (false);
}
//--- 通知成功操作
PrintFormat("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order);
return (true);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
另见 测试交易策略、处理优化结果、OnTesterDeinit、 OnTesterPass、ParameterGetRange、ParameterSetRange
# 13.13 OnTesterDeinit
在EA优化之后,当TesterDeinit事件发生时在EA中调用这个函数。
void OnTesterDeinit(void);
返回值 无返回值
注意 TesterDeinit事件在策略测试中的EA优化完成后生成。
带有OnTesterDeInit()或OnTesterPass()事件处理程序的EA会在优化开始期间,在单独的程序端图表上自动下载。它包括测试中已经制定的交易品种和周期。该函数的设计目的是为最终处理所有的优化结果。
请记住由测试代理使用FrameAdd()函数发送的优化框架可能会绑定在一起,并需要时间来交付。因此,不是所有的框架,以及TesterPass事件,可能在优化结束之前到达,并在OnTesterPass()中处理的。如果您想要得到OnTesterDeinit()中的所有迟到的框架,请使用FrameNext()函数来放置代码块。
另见 测试交易策略、处理优化结果、TesterStatistics、 OnTesterInit、OnTesterPass、ParameterGetRange、 ParameterSetRange
# 13.14 OnTesterPass
当 TesterPass事件发生时,在EA中调用这个函数来处理EA优化期间的新数据框架。
void OnTesterPass(void);
返回值 无返回值
注意 TesterPass事件在接收策略测试中EA优化期间的框架时自动生成。
带有OnTesterDeInit()或OnTesterPass()事件处理程序的EA会在优化开始期间,在单独的程序端图表上自动下载。它包括测试中已经制定的交易品种和周期。这个函数意味着处理优化期间从测试代理接收的框架。包含测试结果的框架应该使用FrameAdd()函数,从OnTester()处理程序来发送。
请记住由测试代理使用FrameAdd()函数发送的优化框架可能会绑定在一起,并需要时间来交付。因此,不是所有的框架,以及TesterPass事件,可能在优化结束之前到达,并在OnTesterPass()中处理的。如果您想要得到OnTesterDeinit()中的所有迟到的框架,请使用FrameNext()函数来放置代码块。
完成OnTesterDeinit()优化之后,可以使用FrameFirst()/FrameFilter和FrameNext()函数再次分类所有接收的框架。
另见 测试交易策略、处理优化结果、OnTesterInit、 OnTesterDeinit、FrameFirst、FrameFilter、 FrameNext、FrameInputs