请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364704 新帖:25

Simulink: EA 交易开发人员指南

舞蹈家发表于:4 月 17 日 17:11回复(1)

简介

关于 Matlab 的广泛可能性,多篇文章中都有描述。 更确切地说,此款软件能够拓宽程序员开发“EA 交易”所用工具的范围。 在本文中,我会尽自己的能力,把 Simulink 这样功能强大的 matlab 软件包的作用讲清楚。

我想为开发自动化交易系统的交易者们另辟一条新路。 之所以转用这种方法,是受到交易者所面对问题的复杂性的启发 - 自动化交易系统的创建、验证及测试。 我不是专业的程序员。 正因如此,对于我来说,要进行自动化交易系统开发,“由简入繁”是最最重要的原则。 那么,我所认为的简单又是怎样呢? 首先,是创建系统过程的可视化,及其作用的逻辑。 还有,手写代码要尽可能地少。 这些期望与 Simulink® 软件包的能力有着惊人的吻合,Simulink® 软件包是一款知名的 MATLAB 产品,是数学计算可视化工具领域的全球领导者。

在本文中,我将尝试根据 Matlab 软件包完成自动化交易系统的创建和测试,然后再编写一个 MetaTrader 5 “EA 交易”。而且,事后检验会采用 MetaTrader 5 的所有历史数据。

为避免术语混淆,我会采用空泛一点的词 - MTS - 来称呼在 Simulink 中工作的交易系统;而工作于 MQL5 中的,则简单地称为“EA 交易”。


1. Simulink 与 Stateflow 基础知识

在我们开始具体操作之前,有必要介绍一下理论上(最差也能够实现)的若干形式。

Simulink® 软件包作为 MATLAB 的一部分,能够帮助用户对动态系统进行建模、模拟和分析。 此外,它还可能就系统性质提出问题,予以模拟,然后再观察会发生什么。

利用 Simulink,用户可以从头开始构建一个模型,也可以对现有的模型进行修改。 该软件包支持线性与非线性系统的开发,根据离散、连续及混合行为实施创建。

该软件包的主要属性,请见开发人员的 网站:

  • 预定义块广泛且可扩展的库;
  • 直观方框图构成与管理的交互式图形编辑器;
  • 通过将模型分割成为设计元素的多个层级,来管理复杂设计的能力;
  • 用于浏览、创建、配置和搜索与您模型相关的所有信号、参数、属性及生成代码的“模型浏览器” (Model Explorer);
  • 允许您连接其它模拟程序、加入手写代码的应用程序接口 (API) ;
  • Embedded MATLAB™ 功能块,用于将 MATLAB 算法引入 Simulink 以及嵌入式系统实施;
  • 模拟模式(常规、加速器、快速加速器),以解释说明的方式运行模拟,或是以编译的 C 代码的速度、利用固定或可变步幅解算器运行模拟;
  • 图形调试器与分析器,检查模拟结果,然后再诊断您设计中的性能和意外行为;
  • 完全访问 MATLAB,从而对结果实施分析和可视化,自定义建模环境,并定义信号、参数和测试数据;
  • 模型分析与诊断工具,用以确保模型一致性和识别建模错误。

好了,我们马上就来研究研究 Simulink 环境吧。 它是利用下述两种方式、由一个已经打开的 Matlab 窗口初始化:

  1. 利用命令窗口中的 Simulink 命令;
  2. 利用工具栏上的 Simulink 图标。

图 1. Simulink 的初始化

图 1. Simulink 的初始化

执行该命令时,库浏览窗口(Simulink 库浏览器)就会出现。

图 2. 库浏览器

图 2. 库浏览器


该浏览器窗口中包含一个 Simulink 库组件的树状图。 想要查看库中某个特定部分,只需用鼠标点选,之后库活动部分的一系列图标组件就会显示于 Simulink 库浏览器窗口的右侧部分。图 2 所示为 Simulink 库的主视图部分。

利用浏览器菜单或其工具栏上的按钮,您可以打开一个窗口,以创建一个新模型或上传现有模型。 我要提醒大家的是:所有的 Simulink 操作都是搭配一个打开的 MATLAB 系统进行的,而在该系统当中,只要其输出由建模程序提供,就有可能监控操作的执行。


图 3. Simulink 空白窗口

图 3. Simulink 空白窗口


首先,我们来更改模型中的几个参数。 打开Simulation(模拟)-->Configuration Parameters(配置参数)。 此窗口中有多个选项卡,每个选项卡中又有很多参数。 我们感兴趣的是默认的 Solver 选项卡,在这里可以设置 Simulink 建模系统解算器的相关参数。

在 Simulation time (模拟时间)中,modeling time (建模时间)通过开始时间 - Start time (通常为 0),以及结束时间 - Stop time 来设定。

考虑到我们的任务,我们为 Start time分配的值为 1,Stop time保留原样。

在solver的备选项中,我还将Type改为 Fixed-step,solver 改为 discrete,而步幅 (fixed-step size) 则改为 1。

图 4. 参数窗口配置


Simulink 环境是通过一个基于有限状态自动理论的事件驱动型建模软件包子系统 - Stateflow 成功完成的。 它允许我们根据一系列的规则(指定事件和应对此类事件的措施),呈现该系统的工作。

Stateflow 软件包的图形用户界面具有下述组件:

  • SF 图表的图形编辑器;
  • Stateflow 浏览器;
  • Stateflow 搜索器,用于搜寻 SF 图表内的必要对象;
  • SF 模型调试器;
  • Real Time Workshop (实时工作间),一个实时代码生成器。

位于 Stateflow 部分且最常用的方框图(图表)。 我们来检查一下。

从库中将该代码块拽出,双击以打开方框图。 会出现一个 SF 图表编辑器空白窗口。 可将其用于创建 SF 图表及其调试,从而获取所需的函数。

左侧直立放置的是工具栏。 有 9 个按钮:

  1. 状态;
  2. 历史节点;
  3. 缺省转换;
  4. 连接节点;
  5. 真值表;
  6. 函数;
  7. 嵌入式 MATLAB 函数;
  8. 框;
  9. Simulink 函数调用。

遗憾的是,本文中不可能详细讨论到每一个元素。 所以,我要限制自己,只简要地描述那些模型中会用到的元素。 更多详情,请参阅 Matlab Help 部分,或是登录开发人员网站。

图 5. 编辑器中的 SF 图表视图

图 5. 编辑器中的 SF 图表视图

“状态”是 SF 图表中的关键对象。 通过圆角矩形表示。

可以独占,亦可并行。 每一个状态都可能成为父层级、拥有子层级。 状态分活动、非活动状态,它们可执行特定流程。

转变是通过一种带箭头的曲线表示,它会将状态与其它对象连接起来。 鼠标左键点击源对象,再将鼠标指针指向目标对象,即可实现转换。 转换可能有其自己的条件 - 记录于尖括号中。 转换的流程于尖括号中指明,如果满足条件,则予执行。 于目标对象确认过程中执行的流程,则由一个斜杠表示。

替代节点(连接节点)呈圆形,允许通过不同路径转换,而且每种路径都由一个特定条件定义。 在这种情况下,对应特定条件的转换就会被选中。

利用 Stateflow 过程语言的语句,该函数作为一个流程图呈现。 一种会呈现转换与替代节点所用逻辑结构的流程图。

“事件”也是 Stateflow 的一个重要对象,隶属于非图形对象组。 该对象可以启动 SF 图表的流程。

而该流程(操作)也是非图形对象。 它可以调用函数,分配一个特定的事件、转换等等。

SF 模型中的数据显示为数值。 数据不会显示为图形对象。 它们可在模型中的任何层级被创建,且具备属性。

2. 交易策略描述

现在,简单地说一下交易。 出于我们的培训目的考虑,“EA 交易”会非常简单,至少也可以说是很初级。

自动化交易系统会基于信号建仓,基本上就是以周期为 21 和 55(黄金分割数)穿越指数移动平均线后,平均收盘价。 所以,如果 EMA 21 从下到上穿越 EMA 55,则建一个长仓;否则,则建短仓。

出于噪声滤除考虑,会在K柱以在21/55穿越出现之后建立的柱的价格建仓。我们会根据 EURUSD H1 交易。 只会建一个仓位。 只有在达到Take Profit(获利) 或Stop Loss(止损)水平时才会收盘。

我想说的是,在自动化交易系统开发和历史事后检验的过程中,整体交易状况中确实有某些简化。

比如,系统不会检查某个信号的经纪人执行情况。 接下来,我们会将交易限制添加到 MQL5 中的系统核心。


3. Simulink 中某交易策略的建模

开始之前,我们需要将历史价格数据上传到 Matlab 环境中。 我们会利用一个 MetaTrader 5 脚本,同时将其保存 (testClose.mq5)。

在 Matlab 中,还可以利用一个简单的 m 脚本 (priceTxt.m) 加载此类数据(开盘价、最高价、最低价、收盘价、点差)。

我们利用 movavg (Matlab 标配功能)创建指数移动平均线数组:

[ema21, ema55] = movavg(close, 21, 55, 'e');

以及柱索引的一个辅助数组:

num=1:length(close);

我们创建下述变量:

K=3; sl=0.0065; tp=0.0295;

建模过程开始。

创建一个 Simulink 空白窗口,将其命名为 mts 保存。 下述操作皆有视频格式的记录。 如果有什么不太清楚或是根本就不明白,您可以找出该视频查看我的操作。

保存模型时,系统可能会出现下述错误:

??? 文件 "C:\\Simulink\\mts.mdl" 中包含与当前字符编码不兼容的字符,windows-1251。为避免该错误,选择下述一种做法:
1) 利用 slCharacterEncoding 函数将现有的字符编码更改为下述某一种: Shift_JIS, windows-1252, ISO-8859-1。
2) 删掉不被支持的字符。 第一个不被支持的字符位于第 23 行,字节偏移量 15。

为将其消除,您只需关闭所有的模型窗户,并利用下述命令更改其编码:

bdclose all
set_param(0, 'CharacterEncoding', 'windows-1252');

我们来指定模型的信息来源。

此类信息来源的角色,将由 MetaTrader 5 的历史数据充当,其中包含开盘价、最高价、最低价和收盘价。 此外,我们还会考虑到点差,尽管它最近变得相对地不固定。 最终,我们记录下柱的开盘时间。 出于建模目的考虑,初始数据的某些数组会被解释为一个信号,作为时间离散点上某时间函数的一个向量值。

我们创建一个 "FromWorkspace" 子系统,以从 Matlab 工作区检索数据。 从 Simulink 库浏览器中,选择 Ports & Subsystems 部分。 用鼠标将 "Subsystem" 块拖拽到 Simulink 模型窗口。 点击该子系统,将其重命名为 "FromWorkspace"。 然后,鼠标左键在代码块上双击进入,从而创建输入与输出变量,以及系统常量。

要在库浏览器中创建信号源,选择 Signal Processing Blockset (信号处理模块集)和源(信号处理源)。 用鼠标将 "Signal from Workspace" (工作区信号)块拖拽到 FromWorkspace 模型的子系统窗口中。 因为此模型有 4 个输入信号,所们只需复制该代码块,再创建它的 3 份副本。 我们马上就指定,按代码块处理哪些变量。 为此,双击该代码块,并于属性中输入变量名称。 变量会是: open, ema21, ema55, num。 我们为其命名如下: open signal, ema21 signal, ema55 signal, num signal。

现在,从 "Commonly used blocks" (常用代码块) Simulink 部分,我们添加一个代码块以创建一个通道 (Bus Creator)。 打开代码块,将输入数量改为 4。利用 Bus Creator 代码块的输入连接 open signal、ema21 signal、ema55 signal、num signal 代码块。

此外,我们还有 5 个输入常量。 "Constant" 代码块从 "Commonly used blocks" 部分添加。 作为值 (常量值),我们指定变量的名称: spread, high, low, tp, sl:

  • spread - 此为点差值数组;
  • high - 此为最高价格值数组;
  • low - 此为最低价格值数组;
  • tp - 按绝对价值计算的获利值;
  • sl -按绝对价值计算的止损值。

我们将块命名如下: spread array, high array, low array, Take Profit, Stop Loss。

在 "Ports & Subsystems" Simulink 部分中,选择输出端口代码块 (Out1) ,将其移至子系统窗口。 制作输出端口的 5 个副本。 我们会把第一个与 Bus Creator 代码块连接,其它的则交替与 spread、high、low、Take Profit 和 Stop Loss 代码块连接。

我们将第一个端口重命名为 price,其它的 - 则按输出变量的名称重命名。

要创建一个交易信号,我们从 Simulink 部分的 "Mathematical Operations" (数学运算),插入加法代码块 (Add) 。 我们称其为 emas 微分。 我们会在代码块内,将信号列表从 c + + 改为 + -。 利用 Ctrl+ K 组合键,将代码块顺时针转动 90 °。 将 ema21 信号块连接到 "+" 输入,将 ema55 信号连接到 "-"。

然后,从 Signal Operations (信号操作)的 Signal Processing Blockset(信号处理块集)部分,插入 Delay (延迟)代码块。 我们将其命名为 K Delay。 在此代码块的 Delay (示例)字段中,我们输入 K 变量的名称。 将其连接到前一个代码块。

emas 微分代码块与 K Delay 设定了计算控制信号的开端(级别差异),仅限建模阶段 - 此时有变化。 而我们稍后即会创建的子系统,如其信号级中有至少一个元素发生变动,则会被激活。

之后,通过 Simulink "Commonly used blocks" 部分,我们再添加一个多路器 with 和 (Mux) 代码块。 还是顺时针 90 ° 旋转代码块。 我们就会将延迟代码块的信号线一分为二,并将其与多路通道连接。

通过 Stateflow 部分插入一个 Chart 代码块。 进入图表。 添加 2 个输入事件(Buy 和 Sell)和 2 个输出事件(OpenBuy 和 OpenSell)。 Buy 事件的触发值 (Trigger) ,我们会设置为 Falling (由一个负值开端激活子系统);至于 Sell 事件,我们会设置为 Rising (由一个正值开端激活子系统)。 OpenBuy 与 OpenSell 事件的触发值 (Trigger) ,我们会设置到函数调用的位置 (Calling),(子系统的激活由给定的 S 函数的逻辑确定)。

我们以默认方式,利用 3 个替代节点创建一个转换。 我们会通过向第二个节点的转换连接第一个节点,为其设置 Buy {OpenBuy;} 的条件和流程;至于第三个,则为其设置 Sell {OpenSell;} 的流程。 将图表输入与该多路通道连接,而两个输出则与另一个多路通道(可由第一个复制得来)连接。 最后一个代码块连接到输出端口(从一个类似的复制而得),并称其为 Buy/Sell。

哦,差点忘了! 想要模型正常运行,我们需要创建一个虚拟通道对象 - 将其置于 Matlab 工作区内。 为此,我们从 Tools (工具)菜单进入 Bus Editor。 再于编辑器内选择 Add Bus 项。将其命名为 InputBus。

根据输入变量的名称插入元素: open, ema21, ema55 和 num。 打开 Bus Creator,勾选 Specify properties via bus object (通过 bus 对象指定属性)旁边的复选框。 换言之,我们将自己的代码块与创建的虚拟通道对象连接起来。 虚拟通道意味着信号仅以图形方式组合,且不会影响到内存的分配。

于子系统窗口中保存变更。 我们对于 FromWorkspace 子系统的操作到此结束。


现在该创建“黑盒”了。 所谓“黑盒”会是一个代码块,基于输入信号处理信息并做出交易决定。 当然,它还需要由我们亲手创建,而不是靠计算机程序。 毕竟,只有我们才能根据情况做出决定,在何种情况下系统应当执行交易。 此外,该代码块还要以信号的形式,展示已完成交易的相关信息。

所需的这个代码块被命名为 Chart,位于 Stateflow 部分。 我们都已经掌握了,对不对? 我们利用“拖放法”,将其移至模型窗口。

图 6. 输入子系统代码块与 StateFlow 图表

图 6. 输入子系统代码块与 StateFlow 图表


打开图表,将我们的数据输入进去。 首先,模仿之前 FromWorkspace 子系统的做法,我们来创建一个通道对象。 但是,与之前不同的是,该对象不再从工作区为我们提供信号,而是会返回获取的结果。 因此,我们称其为 OutputBus。 其元素会变为: barOpen, OpenPrice, TakeProfit, StopLoss, ClosePrice, barClose, Comment, PositionDir, posN, AccountBalance。

现在我们开始构造。 图表窗口中会显示缺省转换 (# 1)。

至于条件和流程,我们应指明:

[Input.num>=56 && Input.num>Output.barClose] {Output.barOpen=Input.num;i = Input.num-1;Output.posN++;}

该条件意味着,如果输入柱的数量不低于 56,以及,如果输入柱高于上一仓位的收盘柱,则数据会被处理。 之后,开盘柱 (Output.barOpen) 会通过索引变量 i (索引,从 0 开始),被分配为输入柱的数量,而建仓数量按 1 递增。

只有在建仓不是第一次的情况下,才能执行第 2 种转换。 否则执行第 3 种转换 - 为账户余额变量 (Output.AccountBalance) 赋值 100000。

如果图表通过 OpenBuy 事件初始化,则执行第 4 种转换。 这种病况下,仓位会被指向购买 (Output.PositionDir = 1),开盘价会变成与开盘柱价格相同,考虑点差 (Output.OpenPrice = Input.open + spread [i] * 1e-5)。 输出信号 StopLoss 与 TakeProfit 的值也会被指定。

如果出现 OpenSell 事件,则该流程遵循第 5 种转换,为输出信号设定相关值。

第 6 种转换实现的前提是长仓,否则该流程遵循第 7 种转换。

第 8 种转换会检查最高柱价格是否达到了获利水平,或者最低柱价格是否达到了止损水平。 否则,索引变量的值会再加 1(第 9 种转换)。

第 10 次转换会验证止损衍生的条件: 柱的最低价格已经穿越了 Stop Loss 水平。 如果确认,则流程会遵循第 11 种转换,然后是第 12 种,并在此定义平仓与建仓的价格差异值、当前账户余额以及收盘柱的索引值。

如果第 10 种转换未被确认,则会于 Take Profit (第 13 种转换)处平仓。 再然后,从第 14 种开始,流程又会遵循第 12 种转换。

针对短仓转换的流程和条件与之相反。

最后,我们完成了图表中新变量的创建。 为了自动将其整合到我们的模型中,我们需要通过点击 "Start Simulation" (开始模拟)按钮,直接在图表窗口中运行该模型。 看起来就像是音乐播放器上的“播放”按钮一样。 这时,Stateflow Symbol Wizard (SF 主对象)会启动,并建议保存创建的对象。 按下 SelectAll (选择全部)按钮,再按下 Create(创建) 按钮。 对象已创建。 现在我们打开模型浏览器。 点击左侧 Model Hierarchy (模型层次)中我们的 Chart (图表)。 我们按数据类型(DataType)为对象排序 。

利用 "Add" 与 "Data" 菜单命令添加更多数据。 我们将第一个变量命名为 Input。 将 Scopes 的值改为 Input,Type 改为 "Bus: <bus object name>。 然后直接在该字段中输入之前创建的通道 InputBus 的名称。 如此一来,我们的 Input 变量即成为 InputBus 类型。 我们将 Port 的值设为 1。

针对 Output 变量采用相同的做法。 只是它必须成为 Output Scope 与 Output Bus 类型。

我们将变量 high、low、sl、tp 和 spread 的范围更改为 "Input" 值。 我们按下述顺序分别设置端口编号: 3, 4, 6, 5, 2.

我们还要将变量 Lots (手数)的范围改成 Constant (常量)。 在 "Value Attributes" (值属性)选项卡输入 1,OpenBuy 与 OpenSell 事件 - 针对 "Initial" 字段中的 Input (右侧)。将 events 中的触发值改为 "Function call")。

创建一个内部变量 len,范围为 Constant。 在 "Value Attributes" 选项卡的 "Initial value" 字段,输入一个 m-函数长度(收盘)。 由此,它会与位于 Matlab 工作区的收盘数组的长度一致。

至于 high 和 low 变量,我们会在 Size 字段中输入一个 [len 1] 值。 由此,我们已经在内存中将 high 和 low 数组大小作为 [len 1] 值保存。

我们还要在 "Value Attributes"(值属性) 选项卡上指明变量 K,在 "Initial value" (初始值)字段(右侧)中指明 K 的实际变量,该变量取自工作区。

结果是,我们已有一个图表子系统,其中包含 7 个输入端口和 1 个输出端口。 我们定位代码块的方式,是要让输入 input events () 端口位于底部。 重命名 "Position handling" 代码块。 就在图表中,还会呈现代码块的名称。 通过适当的端口,将 FromWorkspace 子系统与 "Position handling" (仓位处理)的代码块合并。 并更改代码块的颜色。

必须注意的是:只有被输入的 OpenBuy 或 OpenSell 事件“唤醒”,"Position handling" 子系统才会工作。 我们通过这种方式优化子系统的操作,从而避免不必要的计算。

图 7. FromWorkspace 与 Position handling 子系统


现在,我们必须创建一个子系统,从而在 Matlab 工作区内呈现处理结果,并与 "Position handling" 子系统合并。 这是最最简单的任务了。

我们创建一个 "ToWorkspace" 子系统,用于获取工作区中的结果。 重复我们曾在创建 "FromWorkspace" 子系统时走过的步骤。 于库浏览器中选择 Simulink Ports & Subsystems 部分。 用鼠标将 "Subsystem" 块拖拽到 Simulink 模型窗口。 点击 "Subsystem",将其重命名为 "ToWorkspace"。 将代码块与 "Position handling" 子系统合并。

想创建变量,鼠标左键双击代码块进入。

因为子系统会从 OutputBus (非虚拟总线)对象接收数据,所以我们需要从该通道选择信号。 为此,我们选择库浏览器中的 Simulink "Commonly used blocks" 部分,然后添加一个 "Bus Selector" (总线选择器)。 此代码块将有 1 个输入信号和 2 个输出信号,而我们需要 10 个此类信号。

我们将该代码块连接至输入端口。 按下 "Start simulation"(开始模拟) 按钮(这就是我们的“播放”按钮)。 编译器即开始构建模型。 不会构建成功,但会为总线选择块创建输入信号。 如果我们进入代码块,就会看到所需的信号显示于窗口的左侧,通过 OutputBus 传递。 利用 "Select" (选择)按钮将其全选,并移至右侧 - "Selected signals" (选定的信号)。

图 8. 总线选择器代码块参数

图 8. 总线选择器代码块参数


再次参照 Simulink 库浏览器的 "Commonly used blocks" 部分,并添加 Mux 多路通道块。 它会指明输入的数量,其值为 10。

之后进入 Simulink 库浏览器的 "Sinks" 部分,并将 ToWorkspace 代码块移至子系统窗口。 我们在这里指明变量 "AccountBalance" 的新名称,并将输出格式(保存格式)由 "Structure" (结构)改为 "Array"(数组)。 利用多路通道合并代码块。 删除输出端口,因为它已经没用了。 自定义代码块的颜色。 保存窗口。 子系统准备就绪。

构建模型之前,我们要验证工作区内的变量是否存在。 下述变量必须存在: InputBus, K, OutputBus, close, ema21, ema55, high, low, num, open, sl, spread, tp。

我们将 Stop Time 值作为参数设置以定义 num (末尾)。 意味着被处理的向量的长度,该长度由 num 数组的最后一个元素设定。

开始构建模型之前,我们需要利用下述命令选择一个编译器:

mex-setup

Please choose your compiler for building external interface (MEX) files:
Would you like mex to locate installed compilers [y] / n? y

选择一个编译器:

[1] Lcc-win32 C 2.4.1 in C:\PROGRA~2\MATLAB\R2010a\sys\lcc
[2] Microsoft Visual C++ 2008 SP1 in C:\Program Files (x86)\Microsoft Visual Studio 9.0
[0] None

编译器: 2

大家看到了,我选择的是 Microsoft Visual C ++ 2008 编译器 SP1。

开始构建吧。 按下 "Start simulation" 按钮。 出错一个错误: Stateflow 界面错误: 端口宽度不匹配。 输入 "spread"(#139) 预期一个标量。 信号是带有 59739 个元素的一维向量。

变量 "spread" 不能是双型,而要从 Simulink 继承其信号类型。

在模型浏览器中,针对此变量,我们指定 "Inherit: Same as Simulink" (继承:与 Simulink 相同);在 Size 字段中,指定 "-1"。 保存变更。

再次运行模型。 编译器可用了。 它会呈现一些小警告。 而且,在不到 40 秒的时间内, 该模型可处理近 60,000 个柱的数据。 交易由 '2001 .01.01 00:00 ' 到 '2010 .08.16 11:00' 执行。 未平仓总额为 461。您可以在以下剪辑中看到该模型的作用方式。



4. MQL5 中策略的实施

现在,我们的自动化交易系统已利用 Simulink 编译完毕。 我们需要将这一交易理念转入 MQL5 环境。 我们必须处理好 Simulink 代码块与对象,通过它们来表达我们进行“EA 交易”的逻辑。 现在的任务,是将这套逻辑交易系统转入 MQL5 “EA 交易”。

但是,要注意的是,有些代码块并没有通过某种方式在 MQL5 代码中定义的必要,因为它们的函数可以隐藏。 我会试着在实际代码中,尽可能多地讲解哪一行关联哪一个代码块的相关详情。 有时这种关系可能是间接关系。 而且,有时它还可以体现出代码块或对象的接口连接。

本部分讲解开始之前,我想让您关注一篇文章 - 《针对初学者以 MQL5 编写“EA 交易”的分步指南》。 本文讲述了利用 MQL5 编写“EA 交易”的主体思想和基本规则,内容易于掌握。 但现在我可不会详述这些内容。 我会用到那里的几行 MQL5 代码。

4.1 “FromWorkspace”子系统

比如说, "FromWorkspace" 子系统中有一个 "open signal"(打开信号) 块。 在 Simulink 中,想在事后检验期间获取开盘柱价格,就需要它;而想要在接收到交易信号的情况下,以此价格建仓,也需要它。 很明显,MQL5 代码中没有该代码块,因为“EA 交易”会紧随交易信号收到之后请求价格信息。

我们需要在“EA 交易”中处理从移动平均线接收到的数据。 因此,我们会为其创建动态数组和对应的辅变量,比如句柄。

int ma1Handle;  // 移动平均线1的指标句柄:“ema21 信号”模块
int ma2Handle;  // 移动平均线2的指标句柄:“ema55 信号”

ma1Handle=iMA(_Symbol,_Period,MA1_Period,0,MODE_EMA,PRICE_CLOSE); // 获取移动平均线1的句柄  
ma2Handle=iMA(_Symbol,_Period,MA2_Period,0,MODE_EMA,PRICE_CLOSE); // 获取 移动平均线2的指标句柄 

double ma1Val[]; // 存储每个柱形的移动平均线1的动态数组:“ema21 信号”
double ma2Val[]; // 存储每个柱形的移动平均线2的动态数组:“ema55 信号”

ArraySetAsSeries(ma1Val,true);// MA1的指标值数组:“ema21信号”模块
ArraySetAsSeries(ma2Val,true);// MA2的指标值数组:“ema55信号”模块

所有其它能够某种程度上影响移动 ema21 与 ema55 线,均可视为辅变量。

"Take Profit" 与 "Stop Loss" 被定义为输入变量:

input int TakeProfit=135;   // 获利:FromWorkspace子系统中的“获利”
input int StopLoss=60;      // 止损:FromWorkspace子系统中的“止损”
要考虑到 EURUSD 有 5 个有效数位,TakeProfit 与 StopLoss 值需要按如下方式更新:
int sl,tp;
sl = StopLoss;
tp = TakeProfit;
if(_Digits==5)
 {
  sl = sl*10;
  tp = tp*10;
 
}

"spread"、"high" 和 "low" 数组用于提供值,因为它们负责以关联价格数据矩阵的方式供应历史数据,从而确认交易条件。

它们在代码中并无显式呈现。 但是,有理由认为,比如说, "spread" 数组对于构成一连串的卖盘家价格而言必不可少。 而且,其它两个数组也是确定平仓条件所不可或缺,因为它们会在满足某一特定价格水平后于 MetaTrader 5 中自动执行,所以并未于代码中指定。

"num signal" 代码块为辅助性质,所以不会于“EA 交易”的代码中显示。

"emas differential" 代码块会通过查找差异,检查建短仓或长仓的条件。 "K Delay" 会为数组创建一个延迟 - K 值的平均值。

创建了 Buy 或 Sell 事件,它是建仓子系统的一个输入事件。

代码中所有内容表述如下:

// 买入(激活于否定值)
bool Buy=((ma2Val[1+K]-ma1Val[1+K])>=0 && (ma2Val[K]-ma1Val[K])<0) ||
         ((ma2Val[1+K]-ma1Val[1+K])>0 && (ma2Val[K]-ma1Val[K])==0);

// 卖出 (激活于肯定值)
bool Sell=((ma2Val[1+K]-ma1Val[1+K])<=0 && (ma2Val[K]-ma1Val[K])>0)||
         ((ma2Val[1+K]-ma1Val[1+K])<0 && (ma2Val[K]-ma1Val[K])==0);
只靠建仓子系统就可以创建 "OpenBuy" 与 "OpenSell" 事件,而且两个事件都利用相关条件与流程,在 "Position handling" 子系统中进行处理。

 

4.2 “Position handling”(仓位处理) 子系统

该子系统通过处理 OpenBuy OpenSell 事件开始工作。

如果是子系统的第一种转换,条件之一就是存在的柱数不得少于 56 (通过此类条件检查于代码中指明)。

if(Bars(_Symbol,_Period)<56) // “持仓处理”子系统的第1种转换:条件是 [Input.num>=56] 
      {
        Alert("Not enough bars!");
        return(-1);
      
}

转换的第二个条件: 开盘柱的编号必须高于收盘柱的编号 (Input.num; Output.barClose),也就是说,已平仓。

代码中内容表述如下:

//--- <<持仓处理>>子系统的第1种转换:条件是 [Input.num>Output.barClose]

bool IsBought = false;  // 买入
bool IsSold = false;    // 卖出
if(PositionSelect(_Symbol)==true) // 有持仓
 {
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {
      IsBought=true;  // 买入
     
}
   else if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
     {
      IsSold=true;    // 卖出
     
}
  
}
// 检查持仓
if(IsTraded(IsBought,IsSold))
 {
   return;
 
}

//+------------------------------------------------------------------+
//| 检查持仓的函数                       |
//+------------------------------------------------------------------+
bool IsTraded(bool IsBought,bool IsSold)
  {
   if(IsSold || IsBought)
     {
      Alert("Transaction is complete");
      return(true);
     
}
   else
      return(false);
  
}

第 4 种转换负责建买入持仓。

呈示如下:

// <<持仓处理>>子系统的第4种转换处理:开买入持仓
 mrequest.action = TRADE_ACTION_DEAL;                                  // 市价买入
 mrequest.price = NormalizeDouble(latest_price.ask,_Digits);           // 最近的买价
 mrequest.sl = NormalizeDouble(latest_price.bid - STP*_Point,_Digits);  // 设置止损
 mrequest.tp = NormalizeDouble(latest_price.bid + TKP*_Point,_Digits);  // 设置获利
 mrequest.symbol = _Symbol;                                           // 交易品种
 mrequest.volume = Lot;                                              // 所有的交易量
 mrequest.magic = EA_Magic;                                          // EA的编号
 mrequest.type = ORDER_TYPE_BUY;                                       // 买入订单
 mrequest.type_filling = ORDER_FILLING_FOK;                            // 指定交易量且 
                                                                               // 对某一价格来说至少不差于指定的交易量
 mrequest.deviation=100;                                             // 滑点
 OrderSend(mrequest,mresult);
 if(mresult.retcode==10009 || mresult.retcode==10008) // 请求完成或者成功下单
    {
     Alert("A buy order has been placed, ticket #:",mresult.order);
    
}
 else
    {
     Alert("A buy order has not been placed; error:",GetLastError());
     return;
    
}

第 5 种转换负责建卖出持仓。

呈示如下:

// <<持仓处理>>子系统的第5种转换处理:开卖出持仓
 mrequest.action = TRADE_ACTION_DEAL;                                  // 市价卖
 mrequest.price = NormalizeDouble(latest_price.bid,_Digits);           // 最近的卖价
 mrequest.sl = NormalizeDouble(latest_price.ask + STP*_Point,_Digits);  // 设置止损
 mrequest.tp = NormalizeDouble(latest_price.ask - TKP*_Point,_Digits);  // 设置获利
 mrequest.symbol = _Symbol;                                          // 交易品种
 mrequest.volume = Lot;                                             // 交易量
 mrequest.magic = EA_Magic;                                         // EA的编号
 mrequest.type= ORDER_TYPE_SELL;                                      // 卖单
 mrequest.type_filling = ORDER_FILLING_FOK;                           // 指定交易量, 
                                                                              // 且对某一价格来说,不差于订单中指定的
 mrequest.deviation=100;                                             // 滑点
 OrderSend(mrequest,mresult);
 if(mresult.retcode==10009 || mresult.retcode==10008) // 请求完成或者成功下单
    {
     Alert("A sell order placed, ticket #:",mresult.order);
    
}
 else
    {
     Alert("A sell order is not placed; error:",GetLastError());
     return;
    
}

很明显,子分类中的其它转换并未明显体现于“EA 交易”当中,因为相应的流程(止损激活或达到获利水平)已于 MQL5 中自动执行。

"ToWorkspace" 子系统并未于 MQL5 代码中体现,因为它的任务是在 Matlab 工作区内呈现输出。


总结

以一种简单的交易理念为例,我在Simulink中创建了这套自动化交易系统,并于其中就历史数据展开了一次事后检验。 最开始,有个问题在困扰着我: “如果您可以通过 MQL5 代码快速实现一套交易系统,那么,还有必要牵扯到所有这些小题大作的事情吗?”

当然,如果没有创建系统的可视化过程及其工作逻辑,您也可以做到。 但是,做到的却往往只是那些经验丰富的程序员,或者干脆就是天才。 如果交易系统掺入新的条件和函数时,那么,方框图的存在及其作用很明显就是交易者的任务了。

还想提一句:我无意对比 Simulink 与 MQL5 两种语言的能力。 只是想说清楚,您可以怎样利用一个代码块设计,创建一套自动化交易系统。 而 MQL5 程序人员将来可能也会创建一种可视策略构造程序,方便“EA 交易”的编写过程。

全部回复

0/140

量化课程

    移动端课程