开发 EA 构造函数的一次尝试
内容
- 概述
- 1. 使用构造函数后的 EA 功能
- 2. 构造函数通用算法
- 3. 添加标准指标 — 处理指标的 Code.mq5 文件
- 4. 添加自定义指标
- 5. 我们来捕获事务(简化代码)
- 6. 使用构造函数创建 EA(开仓信号)
- 7. 使用构造函数创建 EA(挂单信号)
- 结束语
概述
从一开始,我的预定目标就是使用标准库。 我的首要任务是实现最简单的功能:包括 CTrade 交易类,及执行买入或卖出的方法。 我之所以选择标准库,是因为它可以生成更简洁的代码。以下以脚本形式执行的短代码以交易量 1.0 手执行开多头仓位:
//+------------------------------------------------------------------+ //| Open Buy.mq5 | //| Copyright © 2018-2021, Vladimir Karputov | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018-2021, Vladimir Karputov" #property version "1.001" //--- #include <Trade\Trade.mqh> CTrade m_trade; // trading object //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- m_trade.Buy(1.0); // open Buy position, volume 1.0 lot }
渐渐地,需求变得越来越复杂,我几乎每次在编写新的智能交易系统(EA)时都会遇到交易错误。 故此,我对编写正确代码的渴望变得越来越强烈。 最终,一篇非常重要的文章《交易机器人在市场发布前必须通过的检查》问世了。 当这篇文章发表时,我已经意识到需要针对交易指令的执行进行可靠控制的函数。 从那一刻起,我开始逐渐积攒了大量经过验证的函数,这些函数可以通过复制->粘贴,轻松添加到 EA 之中。
由于 EA 的操作几乎总是涉及到使用指标,因此我开始积攒创建正确指标句柄,以及接收指标数据的函数。
NB: MQL5 样式意味着指标句柄只能创建一次。 作为一条规定,这是在 OnInit 中完成的。
自 2.XXX, 版起,我开始维护两个开发分支 — 正常的面向过程代码,和类形式的代码(类的主要目标是实现多币种 EA)。
在我的工作动向中,构造函数逐渐接受了最流行的设置:
- 止损和止盈,
- 尾随,
- 依据风险百分比、或恒定/最小手数计算手数,
- 控制交易运作内的时间间隔,
- 场内只存在一笔持仓,
- 反转交易信号,
- 在出现翻转信号的情况下强制平仓...
每个输入都牵扯到创建代码模块和新函数。
对于日常使用,我决定在 Trading engine 3.mq5 EA 当中收集所有最流行的函数和完整的输入集合。事实上,这是一个成品 EA,它能把我们从大量日常工作中解脱出来。 我们所要做的就是在每种特定情况下添加/删除函数,或更改代码模块之间的交互。
1. 构造函数之后的 EA 功能
由构造函数创建的 EA 立即拥有多个设置,可以组合这些设置来创建独特的策略。 版本 4.XXX 应用了以下规则:
- 使用当前品种符号(EA 启动时所在图表的品种符号)
- 止盈、止损和尾随都在输入中设定。 Points — 依据报价货币计量的当前品种符号点数大小,例如 “EURSD” 1.00055-1.00045=10 个点。
通过拖动十字线工具,始终可以在品种符号图表上看到 “点数”:
图例 1. 点数
以下是使用构造函数创建的 EA 输入:
- 交易设置
- Working timeframe — 操作时间帧。操作时间帧也许不同于 EA 启动所在的图表时间帧。 这是所创建指标的默认时间帧(如果指标中未明确指定其它时间帧)。 它还用于跟踪新柱线创建的时刻(如果只有在新柱线出现时才能检测出交易信号,或者只有在新柱线出现时才启动跟踪信号)。
- Stop Loss — 止损。(0 – 禁用)。
- Take Profit — 止盈。(0 – 禁用)。
- Trailing on ... — 尾随。在每次即时报价(柱线#0(每次即时报价))或仅在出现新柱线时检查尾随能力 (柱线 #1 (出现一根新柱线))。
- Search signals on ... — 信号搜索位柱线。在每次即时报价(柱线#0(每次即时报价))或仅在出现新柱线时搜索交易信号 (柱线 #1 (出现一根新柱线))。
- Trailing Stop (min distance from price to Stop Loss) — 尾随停止,价格和持仓止损之间的最小距离。 只有在持仓已有盈利、且价格远离开盘价超过尾随停止+尾随步长时,尾随才被激活。尾随操作显示在 TrailingStop 代码图片之中。
- Trailing Step — 尾随步长。
- 仓位大小管理(手数计算)。
- Money management lot: Lot OR Risk — 资金管理手数:手数或风险。手数计算系统。 手数可以是固定的(资金管理=固定手数,手数大小可设置在 资金管理的数值),也可以是动态的 — 每笔交易的风险 %(资金管理=每笔交易的风险百分比,风险百分比设置在货币管理数值)。 您还可以将固定手数为最小手数(资金管理=最小手数)。
- The value for "Money management" — 资金管理数值
- 交易模式
- Trade mode: 仅允许多头仓位, 仅允许空头仓位 以及 允许多头和空头仓位
- DEMA — 自定义指标参数。 这是您最终设置指标及其参数的地方
- DEMA: 均化周期
- DEMA: 水平偏移
- DEMA: 价格类型
- 时间控制 — 操作时间周期 该时间周期表示在其内允许搜索交易信号
- Use time control — 标志,启用/禁用 时间控制
- Start Hour — 周期开始钟点
- Start Minute — 周期开始分钟
- End Hour — 周期结束钟点
- End Minute — 周期结束分钟
- 挂单参数 — 与挂单相关的参数
- Pending: Expiration, in minutes (0 -> OFF) — 挂单生存期 (0 — 禁用)。
- Pending: Indent — 挂单距当前价格的间距(未明确设置挂单价格时)
- Pending: Maximum spread (0 -> OFF) — 最大点差 (0 — 禁用)。 如果当前点差超过指定值,则不会放置挂单(EA 等待点差缩小)
- Pending: Only one pending — 启用/禁用标志。 场内上只允许有一笔挂单
- Pending: Reverse pending type — 启用/禁用标志。 挂单逆向
- Pending: New pending -> delete previous ones — 如果要设置挂单,则所有前期的其它挂单均被删除
- 附加功能
- Positions: Only one — 启用/禁用标志。 场内上只允许有一笔持仓
- Positions: Reverse — 启用/禁用标志。 交易订单逆向
- Positions: Close opposite — 启用/禁用标志。 如果有交易订单,则所有前期持仓都被平仓,以便执行该订单
- Print log — 启用/禁用标志。 显示有关操作和错误的扩展信息
- Coefficient (if Freeze==0 Or StopsLevels==0) — 考虑到停止级别的比率
- Deviation — 指定滑点
- Magic number — EA 独有 ID
2. 构造函数通用算法
SPosition数组在全局程序级别声明(在 EA 头部)。 它由 STRUCT_POSITION 结构组成。 在启动期间,数组的大小为零。 处理交易信号完毕后,数组也会重置到零。
从 OnTick 中调用 SearchTradingSignals 函数。 如果存在信号(场内没有持仓),该函数将形成一笔交易订单(在数组中为每笔交易订单创建一个单独的 STRUCT_POSITION 结构)。在 OnTick 中检查是否有交易订单存在 — 检查 SPosition 数组大小:如果超过零,则存在一笔交易订单 ,该交易订单会被发送给 OpenBuy 或 OpenSell。 在 OnTradeTransaction 中完成对交易请求执行的控制:
图例 2. 通用算法(简单)
通常假设 EA 针对当前品种符号上操作,即 EA 所在图表的品种符号。 例如: 如果 EA 置于 USDPLN 上,则它针对 USDPLN 操作。
2.1. STRUCT_POSITION 结构
该结构是 EA 的核心。 它同时执行两个角色:该结构为交易订单提供设置字段(在搜索交易信号中执行设置)。该结构还拥有管理交易订单执行的字段(控制在 OnTradeTransaction 中完成)。
//+------------------------------------------------------------------+ //| Structure Positions | //+------------------------------------------------------------------+ struct STRUCT_POSITION { ENUM_POSITION_TYPE pos_type; // position type double volume; // position volume (if "0.0" -> the lot is "Money management") double lot_coefficient; // lot coefficient bool waiting_transaction; // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction ulong waiting_order_ticket; // waiting order ticket, ticket of the expected order bool transaction_confirmed; // transaction confirmed, "true" -> transaction confirmed //--- Constructor STRUCT_POSITION() { pos_type = WRONG_VALUE; volume = 0.0; lot_coefficient = 0.0; waiting_transaction = false; waiting_order_ticket = 0; transaction_confirmed = false; } };
一些字段负责交易订单本身,而另一些处理其执行。该结构包含构造函数 — STRUCT_POSITION() 特殊函数。 它在创建结构对象时调用,用于初始化结构元素。
交易订单字段:
- pos_type — 开仓类型 (可以是 POSITION_TYPE_BUY 或 POSITION_TYPE_SELL)
- volume — 开仓量。 若交易量 0.0,则从 'Position size management (lot calculation)' 输入组中获取
- lot_coefficient — 如果该比率超过 0.0,则交易量乘以该比率
控制交易订单执行的字段
- waiting_transaction — 指示交易订单已成功执行,且需要等待确认的标志
- waiting_order_ticket — 执行交易订单时获取的订单索引
- transaction_confirmed — 指示交易订单执行已确认的标志
启用 transaction_confirmed 中的标志后,一笔交易订单将从结构中删除。 因此,如果 EA 操作不涉及交易订单,则结构的大小为零。 在“我们来捕获事务(简化代码)”章节中了解更多有关结构字段和控制的信息。
为什么我要采用这样的算法?
首先,检查 Buy 方法是否为 true 或 false 似乎就足矣了,且在为 true 情况下,则假设交易订单已经执行。 在许多情况下,这种方法实际上是可行的。 但有时即使返回 true 也不能保证结果。 这在有关 Buy 和 Sell 方法的文档中曾经提过:
注意
方法成功完成并不总是意味着交易操作成功执行。 有必要利用 ResultRetcode() 和 ResultDeal() 返回的值来检查交易请求的结果(交易服务器返回代码)。
交易历史记录中存在的记录项可作为交易操作执行的最终和精准确认。 因此,选择了以下算法:如果方法成功执行,则检查 ResultDeal(成交票据),检查 ResultRetcode(请求执行结果代码),并保存 ResultOrder(订单票据)。 订单票据可在 OnTradeTransaction 中找到。
3. 添加标准指标 — 处理 Indicators Code.mq5 文件
为了更加便捷,成品代码模块(声明存储句柄、输入、创建句柄的变量)被收集在 Indicators Code.mq5 EA 里。指标输入和存储句柄的变量位于 EA “头部”里,句柄则在 OnInit 中创建并设置。 请记住,存储句柄的变量名称格式如下:“handle_” + “指标名”,例如 “handle_iStdDev”。 整个 Indicators Code.mq5 的处理归结为复制-粘贴操作。
NB: MQL5 样式意味着指标句柄只能创建一次。 作为一条规定,这是在 OnInit 中完成的。
3.1. 添加 iRVI(相对活力指数,RVI)指标的示例
创建 Add indicator.mq5 EA。在 MetaEditor 中,运行 MQL 向导,例如,点击 并选择智能交易系统(模板)
图例 3. MQL 向导 -> 智能交易系统(模板)
我强烈建议在下一步至少添加一个输入
图例 4. 智能交易系统(模板) -> 加入参数
这能够在代码在输入模块里自动添加字符串:
//--- input parameters input int Input1=9;
MQL 向导已创建一个空 EA。 现在,我们来添加 iRVI(相对活力指数,RVI)指标。 在 Indicators Code.mq5 里,搜索 handle_iRVI (按下 ctrl + F)。 搜索会定位存储句柄的变量:
图例 5. handle RVI
复制新找到的字符串,并将其插入“Add indicator” EA 的头部:
//--- input parameters input int Input1=9; //--- int handle_iRVI; // variable for storing the handle of the iRVI indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit()
继续搜索并找到句柄创建模块:
图例 6. handle iRVI
复制新找到的字符串,并将其插入到 OnInit 中的 “Add indicator” EA 当中:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iRVI handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period); //--- if the handle is not created if(handle_iRVI==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(Inp_RVI_period), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); }
现在,我们来添加指标输入。 在 Indicators Code.mq5,中键点击,指定 Inp_RVI_period,进入输入模块:
图例 7. handle iRVI
复制字符串,并将其插入到输入中:
#property version "1.00" //--- input parameters input group "RVI" input ENUM_TIMEFRAMES Inp_RVI_period = PERIOD_D1; // RVI: timeframe input int Inp_RVI_ma_period = 15; // RVI: averaging period //--- int handle_iRVI; // variable for storing the handle of the iRVI indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+
编译会产生以下错误:m_symbol 和 m_init_error。 这是一种预期行为,因为在代码中存在的这些变量,可于构造函数操作后获得,而创建 “Add indicator” EA 只是出于演示如何处理 Indicators Code.mq5 文件。
4. 添加自定义指标
我们来添加 MA on DeMarker 自定义指标。 首先,它是一个自定义指标,其次,它用到组。 与上一章节类似,创建 “Add custom indicator'” EA。 在这之后,复制自定义指标的输入,并将其插入到 EA:
#property version "1.00" //--- input parameters input group "DeMarker" input int Inp_DeM_ma_period = 14; // DeM: averaging period input double Inp_DeM_LevelUP = 0.7; // DeM: Level UP input double Inp_DeM_LevelDOWN = 0.3; // DeM: Level DOWN input group "MA" input int Inp_MA_ma_period = 6; // MA: averaging period input ENUM_MA_METHOD Inp_MA_ma_method = MODE_EMA; // MA: smoothing type //---
在 Indicators Code.mq5 里,找到 handle_iCustom 变量,它存储自定义指标的句柄,将其插入到 EA:
//+------------------------------------------------------------------+ //| Add custom indicator.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.00" //--- input parameters input group "DeMarker" input int Inp_DeM_ma_period = 14; // DeM: averaging period input double Inp_DeM_LevelUP = 0.7; // DeM: Level UP input double Inp_DeM_LevelDOWN = 0.3; // DeM: Level DOWN input group "MA" input int Inp_MA_ma_period = 6; // MA: averaging period input ENUM_MA_METHOD Inp_MA_ma_method = MODE_EMA; // MA: smoothing type //--- int handle_iCustom; // variable for storing the handle of the iCustom indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit()
在 Indicators Code.mq5 的 OnInit() 中找到创建自定义句柄的模块 ,并将其插入到 EA:
int handle_iCustom; // variable for storing the handle of the iCustom indicator //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iCustom handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA", Inp_DEMA_ma_period, Inp_DEMA_ma_shift, Inp_DEMA_applied_price); //--- if the handle is not created if(handle_iCustom==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(Inp_DEMA_period), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function |
这里我们尚有一些工作要做。 我们需要设置时间帧(InpWorkingPeriod)、指标的路径(我们假设它存储在 Indicators 文件夹的根目录中)、和输入:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create handle of the indicator iCustom handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker", "DeMarker", Inp_DeM_ma_period, Inp_DeM_LevelUP, Inp_DeM_LevelDOWN, "MA", Inp_MA_ma_period, Inp_MA_ma_method); //--- if the handle is not created if(handle_iCustom==INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d", m_symbol.Name(), EnumToString(InpWorkingPeriod), GetLastError()); //--- the indicator is stopped early m_init_error=true; return(INIT_SUCCEEDED); } //--- return(INIT_SUCCEEDED); }
5. 我们来捕获事务(简化代码)
注:这只是一个 EA 的简化版本。 与成熟的构造函数相比,许多函数的代码更短洁。
如果场内没有该 EA 开立的持仓,则将交易请求设置为开立 BUY 仓位。 从 OnTradeTransaction 和 OnTick 的打印输出里可确认开仓。 在 SearchTradingSignals 函数中搜索和编写交易订单:
//+------------------------------------------------------------------+ //| Search trading signals | //+------------------------------------------------------------------+ bool SearchTradingSignals(void) { if(IsPositionExists()) return(true); //--- int size_need_position=ArraySize(SPosition); if(size_need_position>0) return(true); ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_BUY; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY"); return(true); }
如果场内没有该 EA 开立的持仓,且 SPosition 数组大小为零,则将数组大小增加 1。 如此,我们创建了单个 STRUCT_POSITION 结构对象。 这样,按顺序,调用 STRUCT_POSITION() 构造函数。 调用构造函数之后,结构元素被初始化 (例如,volume — 开仓量设置为 0.0,因此,它取自输入组 “Position size management (lot calculation)”)。 现在,只剩下在结构中设置交易订单类型。 在此情况下,它可以解释为:“开立多头仓位”。
交易订单设置完毕后,SPosition 数组由单个结构组成,且结构元素具有以下值:
元素 | 值 | 注意 |
---|---|---|
pos_type | POSITION_TYPE_BUY | 在 SearchTradingSignals 里设置 |
volume | 0.0 | 在结构构造函数中初始化 |
lot_coefficient | 0.0 | 在结构构造函数中初始化 |
waiting_transaction | false | 在结构构造函数中初始化 |
waiting_order_ticket | 0 | 在结构构造函数中初始化 |
transaction_confirmed | false | 在结构构造函数中初始化 |
5.1. 我们在一个新的即时报价来临时进入 OnTick
OnTick 一般原则:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int size_need_position=ArraySize(SPosition); if(size_need_position>0) { for(int i=size_need_position-1; i>=0; i--) { if(SPosition[i].waiting_transaction) { if(!SPosition[i].transaction_confirmed) { if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed); return; } else if(SPosition[i].transaction_confirmed) { ArrayRemove(SPosition,i,1); return; } } if(SPosition[i].pos_type==POSITION_TYPE_BUY) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } if(SPosition[i].pos_type==POSITION_TYPE_SELL) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } } } //--- search for trading signals only at the time of the birth of new bar if(!RefreshRates()) return; //--- search for trading signals if(!SearchTradingSignals()) return; //--- }
在 OnTick 的开头处点击,检查 SPosition 数组的大小(这是 STRUCT_POSITION 结构数组)。 如果数组大小超过零,开始遍历到零,有两种情况存在:
- 如果 waiting_transaction 结构标志为 true (交易订单已准备就绪,需要等待确认), 检查 transaction_confirmed 标志
- 如果为 false,该笔业务尚未得到确认(例如,如果交易订单已发送,新的即时报价已到达,而 OnTradeTransaction 仍未确认,就可能发生这种情况)。 打印相应的消息并由 return 指令退出 — 等待新的即时报价,希望信息在新的即时报价上更新
- 如果为 true,该笔业务已经确认。 从数组中删除结构,并由 return 指令退出
-
如果 waiting_transaction 结构标志为 false (交易订单刚刚指定完毕,尚未执行),启用 waiting_transaction 标志,并把订单重定向至 OpenPosition。 代码块可以简化为
SPosition[i].waiting_transaction=true; OpenPosition(i); return;
但为了更容易理解 EA 构造函数的完整形式,我仍将其保留为这种形式:
if(SPosition[i].pos_type==POSITION_TYPE_BUY) { if(InpCloseOpposite) { if(count_sells>0) { ClosePositions(POSITION_TYPE_SELL); return; } } if(InpOnlyOne) { if(count_buys+count_sells==0) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } else { ArrayRemove(SPosition,i,1); return; } } SPosition[i].waiting_transaction=true; OpenPosition(i); return; } if(SPosition[i].pos_type==POSITION_TYPE_SELL) { if(InpCloseOpposite) { if(count_buys>0) { ClosePositions(POSITION_TYPE_BUY); return; } } if(InpOnlyOne) { if(count_buys+count_sells==0) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; } else { ArrayRemove(SPosition,i,1); return; } } SPosition[i].waiting_transaction=true; OpenPosition(i); return; }
我们继续追踪。 我要提醒您这段代码块:
if(SPosition[i].pos_type==POSITION_TYPE_BUY) { SPosition[i].waiting_transaction=true; OpenPosition(i); return; }
交易订单刚在结构中设定 (在 SearchTradingSignals 函数里),且 waiting_transaction 标志设为 false。如此,设置 waiting_transaction 标志为 true,并将其作为参数传递至 OpenPosition 函数。 这是 OpenPosition 数组中的结构序列编号。 由于交易订单类型为 POSITION_type_BUY,因此应将结构序列号传递给 OpenBuy 函数。
5.2. OpenBuy 函数
函数目标是通过初步测试,发送开立多头仓位交易请求,并立即跟踪结果。
首先检查 - SYMBOL_VOLUME_LIMIT
SYMBOL_VOLUME_LIMIT | 针对此品种符号单一方向上(买入或卖出)开仓和挂单的最大允许总交易量。 例如,如果限额为 5 手,您也许已有 5 手的持仓,并放置了 5 手的卖出限价挂单。 但在这种情况下,您不能再放置一笔买入或卖出的限价挂单(因为一个方向上的总交易量将超过限制)。 |
如果检查未通过,从 SPosition 数组中删除结构元素。
第二个检查 - 保证金
获取交易操作后剩余的自由保证金(FreeMarginCheck),及交易操作所需的保证金(MarginCheck)。 之后,为安全起见,需确保总和至少等于 FreeMarginCheck:
if(free_margin_check>margin_check)
如果检查未通过,打印 free_margin_check 变量,并从 SPosition 数组里删除结构。
第三个检查 - Buy 操作的二元布尔结果
如果 Buy 方法返回 false,打印错误,并设置 waiting_transaction 为 false (最常见的原因是错误 10004,requote - 重报价)。 故此,将在新即时报价上尝试重新开立多头仓位。 如果结果为 true (下面是代码块,其中 Buy 方法返回 true)
if(m_trade.ResultDeal()==0) { if(m_trade.ResultRetcode()==10009) // trade order went to the exchange { SPosition[index].waiting_transaction=true; SPosition[index].waiting_order_ticket=m_trade.ResultOrder(); } else { SPosition[index].waiting_transaction=false; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(), ", description of result: ",m_trade.ResultRetcodeDescription()); } if(InpPrintLog) PrintResultTrade(m_trade,m_symbol); } else { if(m_trade.ResultRetcode()==10009) { SPosition[index].waiting_transaction=true; SPosition[index].waiting_order_ticket=m_trade.ResultOrder(); } else { SPosition[index].waiting_transaction=false; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(), ", description of result: ",m_trade.ResultRetcodeDescription()); } if(InpPrintLog) PrintResultTrade(m_trade,m_symbol); }
之后检查 ResultDeal (成交票据)。
如果成交票据为零,检查 ResultRetcode (请求执行结果码)。 结果返回码为 10009 (例如,交易订单已发送至外部交易系统,譬如交易所,如此成交票据为零)。 设置 waiting_transaction 为 true,而 waiting_order_ticket 包含 ResultOrder (订单票据)。 否则 (返回码不为 10009), waiting_transaction 设为 false,并打印错误消息。
如果成交票据不为零(例如,在同一交易服务器上执行),则执行类似的返回码检查,并将值写入 waiting_transaction 及 waiting_order_ticket。
5.3. OnTradeTransaction
如果交易订单已成功发送,请等待成交已执行的确认,并记录在交易历史记录当中。 在 OnTradeTransaction 里,我们操控 trans 变量 (其为 MqlTradeTransaction 类型的结构)。 该结构有两个有趣的字段 — deal 和 type:
struct MqlTradeTransaction { ulong deal; // Deal ticket ulong order; // Order ticket string symbol; // Symbol name ENUM_TRADE_TRANSACTION_TYPE type; // Trading transaction type ENUM_ORDER_TYPE order_type; // Order type ENUM_ORDER_STATE order_state; // Order state ENUM_DEAL_TYPE deal_type; // Deal type ENUM_ORDER_TYPE_TIME time_type; // Order type by lifetime datetime time_expiration; // Order expiration time double price; // Price double price_trigger; // Stop Limit order trigger price double price_sl; // Stop Loss level double price_tp; // Take Profit level double volume; // Volume in lots ulong position; // Position ticket ulong position_by; // Opposite position ticket };
一旦 OnTradeTransaction 得到 TRADE_TRANSACTION_DEAL_ADD (在历史记录里添上一笔成交),则执行检查: 调用 HistoryDealSelect 尝试从历史记录中选择一笔成交。如果选择失败,则打印错误。 如果交易历史记录中存在成交,则循环遍历 SPosition 数组。 在循环里,仅查看字段 waiting_transaction 被设为 true 的结构,而 waiting_order_ticket 等于所选出成交订单的票据。 如果找到匹配项,则将 transaction_confirmed 设为 true。 这意味着交易订单已被执行和确认。
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { //--- get transaction type as enumeration value ENUM_TRADE_TRANSACTION_TYPE type=trans.type; //--- if transaction is result of addition of the transaction in history if(type==TRADE_TRANSACTION_DEAL_ADD) { ResetLastError(); if(HistoryDealSelect(trans.deal)) m_deal.Ticket(trans.deal); else { Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError()); return; } if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic) { if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) { int size_need_position=ArraySize(SPosition); if(size_need_position>0) { for(int i=0; i<size_need_position; i++) { if(SPosition[i].waiting_transaction) if(SPosition[i].waiting_order_ticket==m_deal.Order()) { Print(__FUNCTION__," Transaction confirmed"); SPosition[i].transaction_confirmed=true; break; } } } } } } }
新即时报价来临时进入 OnTick。 从 SPosition 数组中删除 transaction_confirmed 字段为 true 的结构。 因此,已被发出的交易订单,直至出现在交易历史之前,一直会被跟踪。
6. 使用构造函数创建 EA(开仓信号)
在开发 EA 之前,我们应该思考它的交易策略。 我们来研究一个简单的策略,基于 iDEMA(双重指数移动平均,DEMA)指标。 这是构造函数中设置的默认策略。 只有当新柱线出现时,才会尝试寻找开单信号,而交易信号本身是一个依序上升或下降的指标:
图例 8. DEMA 策略
请记住,任何策略都可以通过调整参数进行大幅修改。 例如,我们可以在保留止盈和止损不变的情况下禁用尾随。 反之亦然:禁用止盈和止损,同时保留尾随。 我们也许会只允许买入或卖出来限制交易方向。 还可以启用 Time control,限制夜间交易,或相反,仅设置在夜间交易。 通过调整 Additional features 组中的参数,交易系统也可以发生重大变化。
一般来说,交易策略的主干是构建在 SearchTradingSignals 函数中,而所有其它参数都是“探测”行情,从而寻找最佳方法的。
故此,我们创建一个新文件 EA(执行图例 3 和图例 4 所示的步骤)。 在步骤 4,为 EA 指定一个独有的名称。其为 iDEMA Full EA.mq5。 结果则是,我们得到以下工件:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.00" //--- input parameters input int Input1=9; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
现在从 Trading engine 3.mq5 复制整段代码,并插入它来替代原来的字符串。 现在是编辑 “EA 头部” 的时候了。 生成的“头部”如下所示:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //+------------------------------------------------------------------+ //| Trading engine 3.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "4.003" #property description "barabashkakvn Trading engine 4.003" #property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)" /* barabashkakvn Trading engine 4.003 */ #include <Trade\PositionInfo.mqh>
令“头部”看起来如下所示:
//+------------------------------------------------------------------+ //| iDEMA Full EA.mq5 | //| Copyright © 2021, Vladimir Karputov | //| https://www.mql5.com/en/users/barabashkakvn | //+------------------------------------------------------------------+ #property copyright "Copyright © 2021, Vladimir Karputov" #property link "https://www.mql5.com/en/users/barabashkakvn" #property version "1.001" #property description "iDEMA EA" #property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)" /* barabashkakvn Trading engine 4.003 */ #include <Trade\PositionInfo.mqh>
现在,如果我们编译它,将不会有错误。 得到的 EA 甚至可以进行交易。
6.1. SearchTradingSignals 函数
这是负责检查交易订单可用性的主要函数。 我们来逐模块研究这个函数。
每根柱线不超过一笔开仓:
if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal return(true);
检查交易时间范围:
if(!TimeControlHourMinute()) return(true);
从指标接收数据。 指标数据被传递到 dema 数组。 借助于 ArraySetAsSeries,可以为其设置反向索引顺序(将 [0] 数组元素对应于图表上最右边的柱线)。 通过 iGetArray 自定义函数接收数据:
double dema[]; ArraySetAsSeries(dema,true); int start_pos=0,count=6; if(!iGetArray(handle_iCustom,0,start_pos,count,dema)) { return(false); } int size_need_position=ArraySize(SPosition); if(size_need_position>0) return(true);
开立多头仓位信号。 如有必要(InpReverse 变量存储 Positions: Reverse 输入值),可以逆转交易信号。 如果交易方向有限制(InpTradeMode 变量存储 Trade mode: 输入值),也会作为参考:
//--- BUY Signal if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=sell) { ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_BUY; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY"); return(true); } } else { if(InpTradeMode!=buy) { ArrayResize(SPosition,size_need_position+1); SPosition[size_need_position].pos_type=POSITION_TYPE_SELL; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL"); return(true); } } }
做空信号的代码模块与其类似。
7. 使用构造函数创建 EA(挂单信号)
EA 的名称为 iDEMA Full EA Pending.mq5。 打开 iDEMA Full EA.mq5,并以新名称保存。
总是应该先制定交易策略,然后才是编写代码。 我们稍微更改一下 第 6 节 使用构造函数创建 EA(开仓信号) 中的策略。做多信号替换为 BuyStop 挂单,而做空信号替换为 SellStop 挂单。 以下参数用于挂单:
- Pending: Expiration, in minutes (0 -> OFF) -> 600
- Pending: Indent — 挂单据当前价格的间隔 (若挂单价格未明确设定) -> 50
- Pending: Maximum spread (0 -> OFF). 如果当前点差超过指定值,则不设置挂单 (EA 等待点差缩小) -> 12
- Pending: Only one pending — 启用/禁用标志。 场内上只允许有一笔挂单 -> true
- Pending: Reverse pending type — 启用/禁用标志。 挂单逆向 -> false
- Pending: New pending -> 删除以前挂单 — 如果要放置挂单,则会删除前期所有其它挂单 -> true
SearchTradingSignals 函数如下所示:
//+------------------------------------------------------------------+ //| Search trading signals | //+------------------------------------------------------------------+ bool SearchTradingSignals(void) { if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal return(true); if(!TimeControlHourMinute()) return(true); double dema[]; ArraySetAsSeries(dema,true); int start_pos=0,count=6; if(!iGetArray(handle_iCustom,0,start_pos,count,dema)) { return(false); } int size_need_pending=ArraySize(SPending); if(size_need_pending>0) return(true); //--- if(InpPendingOnlyOne) if(IsPendingOrdersExists()) return(true); if(InpPendingClosePrevious) m_need_delete_all=true; //--- BUY Signal if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=sell) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP"); return(true); } } else { if(InpTradeMode!=buy) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP"); return(true); } } } //--- SELL Signal if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3]) { if(!InpReverse) { if(InpTradeMode!=buy) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP"); return(true); } } else { if(InpTradeMode!=sell) { ArrayResize(SPending,size_need_pending+1); SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP"); return(true); } } } //--- /*if(InpPendingOnlyOne) if(IsPendingOrdersExists()) return(true); if(InpPendingClosePrevious) m_need_delete_all=true; int size_need_pending=ArraySize(SPending); ArrayResize(SPending,size_need_pending+1); if(!InpPendingReverse) SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP; else SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP; SPending[size_need_pending].indent=m_pending_indent; if(InpPendingExpiration>0) SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60); if(InpPrintLog) Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/ //--- return(true); }
请注意,我们没有在 SPending 结构中设置挂单价格 。 这意味着将用当前价格叠加间隔。
我们收到一个交易信号,该信号仅在点差低于指定值时触发(放置一笔挂单):
图例 9. iDEMA Full EA Pending
文后所附文件:
名称 | 文件类型 | 说明 |
---|---|---|
Indicators Code | EA | 存储句柄、指标输入、指标创建块的变量 |
Add indicator.mq5 | EA | 使用 Add indicator.mq5 的示例 — 添加标准指标 |
Add custom indicator.mq5 | EA | 加入自定义指标的示例 |
Trading engine 4.mq5 | EA | 构造器 |
iDEMA Full EA.mq5 | EA | 在构造器的帮助下创建的 EA — 开仓信号 |
iDEMA Full EA Pending.mq5 | EA | 在构造器的帮助下创建的 EA — 放置挂单信号 |
结束语
我希望这套交易函数能帮助您创建更可靠的智能交易系统,为适应不断变化的市场交易条件做好准备。 尝试这些参数时无需犹豫。 您也可以通过启用某些参数、同时禁用其它参数,来大幅更改策略。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9717