简介
任何交易系统的生存期都可简化为开仓和平仓。这一点毫无疑问。但如果涉及到算法的实现,则有多少编程人员就有多少种选择。每个人都会以自己的方式来解决同样的问题,但殊途同归。
经过多年的编程实践,各种构建“EA 交易”的逻辑和结构的方法已一一试遍。现在可以说已开发出用于所有代码的清晰的模式模板。
该方法不能说具有 100% 的普适性,但它可能改变您设计“EA 交易”的逻辑的方法。而且这不是关乎您想要使用“EA 交易”处理订单的能力。重点是创建交易模型的原则。
1. 设计交易系统的原则和事件源的类型
大多数人采用的设计算法的基本方法,是从开仓起追踪持仓直至平仓。这是一种线性方法。若您想要对代码进行更改 - 由于有大量的条件出现且代码累积带来新的分析方向,这通常会导致极其复杂的情况。
建立交易机器人模型的最佳解决方案是“服务于条件”。其基本原则是 – 不要分析该“EA 交易”条件和其持仓以及订单是如何发生的 - 而是分析我们现在要如何做。该基本原则从根本上改变了交易的管理并简化了代码的开发。
接下来考虑更多细节。
1.1. “服务于条件”原则
正如前文所述,“EA 交易”并不需要知道当前状态是如何达成的。它需要知道的是,根据环境现在要做什么(参数值、存储的订单属性等)。
该原则和“EA 交易”存在于从循环到循环(特别是从订单号到订单号)的事实直接相关,它无需关心订单在上一订单号发生的事情。因此,您必须使用事件驱动的方法管理订单。换言之,“EA 交易”在当前订单号保存自己的状态,作为下一订单号相关决策的出发点。
例如,您必须移除所有“EA 交易”的挂单,然后才能继续分析指标并下达新的订单。我们看到的大部分代码示例使用 "while (true) {try to remove}" 循环或稍稍不那么生硬的 "while (k < 1000) {try to remove; k++;}" 循环。赫兹量化交易软件将跳过该变体,它只对移除命令进行了一次调用而无错误分析。
该方法是线性的,它将占用“EA 交易”不确定的时长。
因此,不循环“EA 交易”而是存储删除订单的顺序才是正确之举,这样在每个新的订单号处尝试删除挂单时都会检查该订单。在这种情况下,“EA交易”通过读取状态参数就会知道,在这一刻它必须删除订单。然后它将尝试删除订单。如果发生交易错误,“EA 交易”将只会在下一个循环开始前阻止进一步的分析和工作。
1.2. 设计的第二个主要原则 - 尽可能地抽象化考虑的持仓方向(买入/卖出)、货币和图表。所有“EA 交易”的功能应以这样一种方式实施,即仅在确实无法避免的极少数情况下分析方向或交易品种(例如,在您考虑开仓的有利价格增长时,尽管存在避免细节的不同选项)。始终尽量避免此类“低水平”设计。这将减少代码和至少两次的函数编写过程。并且可以实现“独立于交易”。
该原则的实施是用宏函数取代订单类型、交易品种参数和依赖性计算参数的显式分析。在后续文章中赫兹量化交易软件将详细介绍该实施。
1.3. 第三个原则 – 将算法细分为逻辑语义(独立模块)
在实践中,我们可以说,最佳方法是将“EA 交易”操作划分为单独的函数。我想您一定会同意,在一个函数中编写“EA 交易”的整个算法是很困难的,而且这也使后续的分析和编辑复杂化。所以我们在 MQL5 中要避免这样做,尤其是 MQL5 现在几乎可提供对环境的完全控制。
因此,逻辑语义(如开仓订单、追踪订单、平仓订单)应根据对环境参数和事件的完全分析彼此单独实施。通过这一方法,“EA 交易”的设计变得灵活起来。您可以很容易地向其添加新的独立模块而不会影响现有模块,或是禁用现有模块而无需更改主代码。
这三项原则使得为所有“EA 交易”创建单一的原型成为可能,您可以很容易地修改该原型或使其适应任何给定任务。
“EA 交易”系统的事件源:
1. 指标。 一个例子是对指标线条值、它们的交叉和组合等的分析。同时,指标可以是:当前时间、从网络获得的数据等。在大多数情况下,指标事件用于以信号指示订单开仓和平仓。较少用于它们的调整(通常为指标的追踪止损订单或挂单)。
例如,指标的实际实施可以是调用一个“EA 交易”,该“EA 交易”分析快速和慢速 MA 与沿交叉方向的进一步开仓的交叉。
2. 现有订单、持仓及其状态。例如,当前损失或利润规模、存在/不存在持仓或挂单、已平仓持仓的利润等。由于相比指标事件它们的关系存在更多选项,这些事件的实际实施要远为宽泛和多样化。
仅基于交易事件的最简单的“EA 交易”示例是重新填入以平均现有持仓,然后将其输出至所需利润。换言之,可用持仓存在的损失将成为下达新平均订单的事件。
或,例如,追踪止损。在价格从上一止损以指定的点数移动至利润时,该函数检查事件。作为结果,“EA 交易”在价格后拉动止损。
3. 外部事件。 虽然此类事件通常不会出现在纯粹的“EA 交易”系统中,但通常而言,在作出决策时需要将其考虑在内。这包括调整订单、持仓,处理交易错误、图表事件(移动/创建/删除对象、按下按钮等)。一般来说,这些是无法在历史数据中验证且仅在“EA 交易”工作时出现的事件。
此类“EA 交易”的一个突出示例是带图形交易控制的交易信息系统。
各种“EA 交易”都是基于这三种事件源的组合。
2. CExpertAdvisor 基类 – “EA 交易”构造函数
“EA 交易”的工作是什么?MQL 程序交互的基本框架如下图所示。
图 1. MQL 程序元素交互的基本框架
如您在框架中所看到的,首先是工作循环的入口(这可以是订单号或计时器信号)。在这一阶段,该订单号可以在第一个程序块中过滤掉而不加以处理。这在“EA 交易”仅需处理新柱的订单号而无需处理所有订单号,或在不允许“EA 交易”工作的情况下发生。
然后程序执行第二个程序块 - 处理订单和持仓的模块,此时事件处理程序块从模块中调用。每个模块仅可查询与其相关的事件。
该序列可称之为带直接逻辑的框架,因其首先确定“EA 交易”将要做什么(使用哪些事件处理模块),然后它才实施如何以及为什么要做(获取事件信号)。
直接逻辑和我们对世界的认知以及泛逻辑一致。毕竟,我们首先是思考具体概念,接下来才能对其进行终结,然后是将它们分类以及识别它们的相互关系。
设计“EA 交易”在这方面也不例外。首先是声明“EA 交易”应该做什么(开仓和平仓、拉动保护止损),然后才是指定在哪个事件中以及如何去做。但在任何情形下反过来都不成立:接收信号然后思考在何处以及如何处理。这是反向逻辑,最好不要使用,否则您将会获得冗长的代码和大量的条件分支。
下面是一个关于反向逻辑和直接逻辑的示例。通过 RSI 信号开仓/平仓。
- 在反向逻辑中,“EA 交易”首先是获得指标值,然后检查信号方向以及您需要对持仓执行的操作:买入开仓和卖出平仓,或反之亦然 - 卖出开仓和买入平仓。也就是说,切入点为获取和分析信号。
- 在直接逻辑中,一切都是相反的。“EA 交易”具有开仓和平仓两个模块,它只需检查执行这些模块的条件。换言之,在进入开仓模块后,“EA 交易”接收指标值并检查其是否是开仓的信号。然后,在进入平仓模块后,“EA 交易”检查其是否是平仓的信号。也就是说,不存在切入点 - 系统状态分析具有独立的工作模块(设计的第一个原则)。
现在,如果您想要为“EA 交易”添加功能,使用第二个变体要比第一个变体容易很多。它足以用来创建事件处理的新模块。
并且在第一个变体中,您将需要修改信号处理的结构或将其作为单独的功能粘贴。
建议: 说明交易系统时,请勿以“1. 获取信号...开仓订单 ”诸如此类的话语开始,而是立即划分出几个部分:“a) 开仓订单的条件,b) 维护订单的条件,等等。”,然后分别分析所需的信号。
要更好地理解这一方法,下图给出了以四种不同的“EA 交易”为上下文的不同工作框架。
图 2.“EA 交易”实施示例
a).“EA 交易”,仅基于某些指标的信号。它能够在信号改变时开仓或平仓。示例 - MA“EA 交易”。
b).带图形交易控制的“EA 交易”。
c).基于指标的“EA 交易”,但添加了追踪止损和操作时间。示例 - 在开立持仓通过 MA 指标进入趋势时利用消息套利。
d).“EA 交易”无指标,带平均持仓。它仅在建立新柱时对持仓参数进行一次验证。示例 - 平均“EA 交易”。
从框架中我们可以看出,使用直接逻辑说明任何交易系统都是极其简单的。
3. “EA 交易”类的实施
使用上文提到的所有原则和要求创建一个类,这将成为未来所有“EA 交易”的基础。
CExpertAdvisor 类应具有的基本功能如下:
1. 初始化:
- 注册指标
- 设置参数的初始值
- 调整至所需交易品种和时间表
2. 获取信号的函数
- 允许的工作时间(交易间隔)
- 确定开仓/平仓或未结订单/已结订单的信号
- 确定过滤器(趋势、时间等)
- 开始/停止计时器
3. 服务函数
- 计算开盘价、止损和获利水平、订单量
- 发送交易请求(开仓、平仓、修改持仓)
4. 交易模块
- 处理信号和过滤器
- 控制持仓和订单
- 引进“EA 交易”函数:OnTrade()、OnTimer()、OnTester()、OnChartEvent()。
5. 取消初始化
- 输出消息、报告
- 清除图表、卸载指标
该类的所有函数可分为三组。嵌套函数的基本框架以及函数的说明如下所示。
图 3.“EA 交易”嵌套函数的框架
1. 宏函数
该小组函数是使用订单类型、交易品种参数和价格值设置订单(未结订单和止损订单)的基础。这些宏函数体现了第二个设计原则 - 抽象化。它们在“EA 交易”使用的交易品种上下文中工作。
转换类型的宏函数根据市场方向 - 买入或卖出 - 工作。因此,为了不创建您自己的常量,最好使用已有常量 - ORDER_TYPE_BUY 和 ORDER_TYPE_SELL。下面给出了宏的一些使用示例以及工作结果。
//--- 类型转换模块
long BaseType(long dir); // 返回指定方向订单的基本类型
long ReversType(long dir); // 返回指定方向订单的反向类型
long StopType(long dir); // 返回指定方向的停止订单类型
long LimitType(long dir); // 返回指定方向的限价订单类型
//--- Normalization macro
double BasePrice(long dir); // 返回指定方向的卖/买价
double ReversPrice(long dir); // 返回反方向的卖/买价
long dir,newdir;
dir=ORDER_TYPE_BUY;
newdir=ReversType(dir); // newdir=ORDER_TYPE_SELL
newdir=StopType(dir); // newdir=ORDER_TYPE_BUY_STOP
newdir=LimitType(dir); // newdir=ORDER_TYPE_BUY_LIMIT
newdir=BaseType(newdir); // newdir=ORDER_TYPE_BUY
double price;
price=BasePrice(dir); // price=Ask
price=ReversPrice(dir); // price=Bid
在开发“EA 交易”时,宏允许您不指定处理方向,并帮助您开发更紧凑的代码。
2. 服务函数
这些函数设计用于处理订单和持仓。与宏函数一样,它们也属于低级函数。为方便起见,可将服务函数分为两个类别:信息函数和执行函数。它们都只执行一种操作,没有任何事件分析。它们执行来自高级“EA 交易”句柄的订单。
信息函数示例: 找出当前挂单的最大开盘价;找出持仓的平仓方式 - 获利或是亏损;获取“EA 交易”订单的订单号和订单号列表,等等。
执行函数示例: 结算指定订单;在指定持仓中修改止损,等等。
这是最大的一组。这是“EA 交易”的整个运行时工作所基于的功能性。可在论坛上找到这些函数的大量示例,网址www.herzqt.com。除此之外,MQL5 的标准库中已经包含了一些承担部分下达订单和持仓工作的类,特别是 - CTrade 类。
但您的任何任务都将要求您创建新的实施或是对已有实施稍加修改。
3. 事件处理模块
该组函数是之前两组函数的高级上部结构。正如前文所述 - 这些函数随时可用,是您的“EA 交易”所构建的程序块。一般而言,它们包含于 MQL 程序的事件处理函数中:OnStart()、OnTick()、OnTimer()、OnTrade()、OnChartEvent()。该组模块数目不多,且这些模块的内容可根据任务作出调整。但是基本上没有什么变化。
模块中的一切都应该是抽象的(第二个设计原则),以便同一模块可以为买入和卖出调用。这是通过宏的帮助实现的。
所以,继续实施
1. 初始化、取消初始化
class CExpertAdvisor
{
protected:
bool m_bInit; // 正确初始化的标识
ulong m_magic; // EA专家系统的编号
string m_smb; // EA运行于的交易品种
ENUM_TIMEFRAMES m_tf; // 时间框架
CSymbolInfo m_smbinf; // 交易品种参数
int m_timer; // 计时器时间
public:
double m_pnt; // 考虑停止位的5/3个小数位报价
CTrade m_trade; // 执行交易订单的对象
string m_inf; // EA运行信息的备注字符串
这是“EA 交易”函数工作所需的最小参数集。
m_smb 和 m_tf 参数专门放置在“EA 交易”属性中,以便很容易地告知“EA 交易”处理的货币和时间周期。例如,如果您分配 m_smb = "USDJPY",“EA 交易”将处理该交易品种而不管之前运行的是何交易品种。如果您设置 tf = PERIOD_H1,则指标的所有信号和分析都将发生在 H1 图表上。