科学家们探索已经存在的东西;工程师们则创造从没有过的东西。
阿尔伯特 · 爱因斯坦
在我的《Simulink:EA 交易开发人员指南》一文中,我建议使用动态系统建立 EA 交易的模型。然而,这种方法仅向设计人员表达了交易系统的一个方面 - 系统的动态行为。专业人士具有能够扩充交易系统开发人员的方法的专门工具。在本文中,我们将讨论如何使用通用工具 - UML 图形语言开发 EA 交易。
一般而言,作为一种图形语言,UML 用于面向对象的软件系统的可视化建模。但是,如我所见,我们能够用其工具来开发一个交易系统。此外,MQL5 属于面向对象的语言家族,这使我们的任务更加容易。
出于建模目的,我选择了免费的非商业软件 Software Ideas Modeler。
UML 如何帮助创建 EA 交易?首先,可以使用能够在语言中使用的图像解决多方面建模的图形问题。其次,可读性。即使一个 EA 大而复杂,UML 的通用性也能够使用图表表示其模块。
如 UML 的开发人员所说,人类感知的具体功能依赖于这样的事实:带有图像的文字比纯文字更容易被理解。
让我们简要讨论一下 UML 的基础。如果您对这一主题有兴趣,可以从网络中数量众多的免费出版物学习 UML 工具。
可以在图中显示 UML 结构(图 1)。
图 1. UML 结构
包括以下构造块:实体(模型元素)、关系(绑定)和图表(表示 UML 模型)。
UML 图表能够以可视化的方式,从不同的角度表示设计的系统。
公共机制包括:规范(语义描述)、修饰(标记模型的重要特性)、通用划分(抽象及其实例、接口和实施)、扩展机制(约束、构造型和标记值)。
架构负责在其环境中高级别的系统表示。“4+1 架构视图”(图 2)最能描述 UML 架构:
图 2. 4+1 架构视图
还应注意,UML 有其自己的规范图层次结构(图 3)。2.2 版语言使用 14 类 UML 图。
图 3. 规范 UML 图
此外,我建议考虑 UML 图的运用的某些特殊案例。因此,出于 EA 开发目的,我们可以从抽象转到使用任何图表的具体情形。再一次指出,UML 图的层次结构提供的交易系统的多方面设计原则对系统和全面地完成交易系统的创建任务做出了贡献。
2.1. 用例图
俗话说,良好的开端是成功的一半。.通常情况下,尽管不是必须的,分析工作以用例图开始。它从用户观点描述系统。
创建时,我们可以:
用例是一系列的步骤,通常定义为了实现一个目标,角色(在 UML 中称为 "actor",参与者)与系统之间的互动。
参与者指定用户或参与主题的任何其他系统扮演的角色。参与者可以代表人类用户、外部硬件或其他主体扮演的角色。
关系是模型的单个元素之间的语义连接。
您可能注意到这类图表非常广义,反映了交易系统而不是其实施的概念本质。但是这才是重点 - 从一般到具体,从抽象到有形。谁说我们不是艺术家呢?我们从一般想法和草图开始绘画。首先,我们在画布上绘制线条。然后添加颜色。绘制细节...
那么,让我们尝试为一个交易系统创建用例图。
作为输入参与者,我选择了以下角色:开发人员 (Developer)、系统分析师 (System analyst)、风险管理员 (Risk manager) 和管理员 (Administrator)。应该指出,这些角色可以由一人或多人担当。我们的交易系统采取什么动作?哪些动作与之有关?
因此,开发人员可创建和实施一个交易系统。此外,开发人员可以参与交易系统的优化。系统分析师优化交易系统。风险管理员负责风险管理。管理员监视交易系统的整体运行。在输出端,我们看到用户 (User) 使用交易系统获益。此角色是交易者 (Trader) 和投资者 (Investor) 等角色的组合。经理 (Manager) 和管理员 (Administrator) 监控交易系统的运行。
图表包含“交易系统”块图。它表示了交易系统的边界,并将其与外部世界隔开。
现在,简单地说明一下参与者与用例之间的关系以及参与者相互之间的关系和用例相互之间的关系。大多数关系通过关联来表示,用实线标记。这表示某个参与者发起了某个用例。因此,风险管理员发起风险管理流程等等。发起用例的参与者是主要的,使用动作结果的参与者是次要的。例如,输出端的经理是次要参与者。
关联 (Association) 可表示参与者发起相应的用例。
泛化 (Generalization) 模拟相应的角色一般性。
扩展 (Extension) 是基础用例及其特殊用例之间的依存关系。
包含 (Include) 定义基础用例与其他用例之间的关系,这些用例的功能行为并不始终都被基础用例使用,而是在额外的条件下使用。
然而请注意,与用例有关的次要角色并不意味着该角色就是次要的。此外,在图中,我们看到交易系统用户通过泛化关系包含交易者 (Trader) 和投资者 (Investor),显示为带有“空心”三角箭头的线条。
图 4. 交易系统的用例图
用例 "Open position"(建仓)和 "Close position"(平仓)又通过泛化与 "Trading"(交易)关联在一起。后一个是另外两个的基础用例。因此,它包含用例 "Manage risk"(管理风险)。其行为是依存用例 "Profit"(盈利)的补充。
因为交易系统盈利是在满足资产的卖出价格高于买入价格这一条件时才会出现,对于这些用例,我使用扩展关系。该图还显示了扩展点,即一个具体的条件,满足该条件时使用 "To profit"(盈利)用例。用带有箭头的虚线显示依存关系,箭头对应于构造型“include”(包含)和“extend”(扩展)。
对于每个用例,您需要创建一个场景,以描述达成预定目标的一系列步骤。可以用几种形式来描述用例。最被认可的形式包括:文字描述、伪代码、活动图、交互图。
应该指出,严格来说,交易者对交易系统感兴趣,而不是图 4 所示的内容。因此,我将进一步建议专注于带有扩展 "To profit" 的用例 "Trading"。
2.2 类图
我们使用类图来描述交易系统的结构。也就是说,我们将以面向对象编程的类来表示交易系统的静态结构模型。因此,我们将反映交易系统的编程逻辑。
在 UML 中,类图是一种静态结构图。它通过显示系统的类、属性和操作符以及类之间的关系来描述系统的结构。
此类图有什么优点?那些有点熟悉面向对象编程语言的人会立即注意到“类”的相似概念。类在 UML 类图中的作用相当于基础构建块。例如,在生成 C++ 代码时,以类模板的形式自动创建 UML 类块。您仅需完成每个方法和属性的实施。
现在,让我们尝试设计一个例子。但是,首先我要提醒您注意《交易机器人的原型》一文。在该文中,作者描述了使用直接逻辑的优点。在我看来,嵌套原则 - “宏-函数-交易模块”非常有效且有用。
例如,我们需要一个能够使用标准库中的交易类的 EA 交易。
使用类块,在类图中创建一个类模型。我将其称为 CTradeExpert。我们为新的类添加某些属性(在 MQL5 中,它们是类的数据成员)。如下:Magic_No、e_trade、e_account、e_deal、e_symbol、e_pnt。我们还插入类 CTradeExpert 的构造函数方法。以图形方式表示,操作如图 5 所示。
图 5. 类 CTradeExpert 的 UML 模型
属性前面的字符 "-" 表示属性的访问权限为 private,# 表示 protected,+ 表示 public。因此,对于属性 Magic_No,访问说明符设置为 private,对于 e_pnt,访问说明符设置为 public,对于其他,访问说明符设置为 protected。一个跟随属性名称的冒号指出属性的数据类型和方法返回的数据类型。例如,属性 Magic_No 的类型为 int,e_trade 的类型为 CTrade,等等。
现在我们不添加任何方法和属性,只是简单地说明我们的类 CTradeExpert 是如何与标准库中的类连接的。为此,向图添加 6 个类块,并分别将它们称为:CTrade、CAccountInfo、CDealInfo、CSymbolInfo、CObject。现在,我们通过与 "use"(使用)构造型的依存关系(带有箭头的虚线)将 CTradeExpert 类的模型与 4 个交易类块关联在一起。
依存关系 (Dependency) 是两个实体之间的一种语义关系,在这种关系中,其中一个实体的独立性的变化可以影响另一个依存实体的语义。
UML 中的构造型是对象行为的描述。
接着,我们使用带有“空心”三角箭头的线条,通过泛化关系将这些块连接到 CObject 块。向标准库类添加备注。现在,我们的 UML 图如图 6 所示。
图 6. UML 类图
现在,我们仅需要使用边栏中 "Generate"(生成)选项卡上的 "Generate"(生成)功能生成代码即可(图 7)。
图 7. 生成代码
最适合的是 С++ 语言。我们使用 C++ 生成 EA 交易类的代码,然后轻松地将其转换为 MQL5。
对于此图,生成的代码如下所示:
// class CTradeExpert { private: int Magic_No; protected: CTrade e_trade; protected: CAccountInfo e_account; protected: CDealInfo e_deal; protected: CSymbolInfo e_symbol; public: double e_pnt; public: void CTradeExpert () { } }; // class CObject { }; // class CTrade : public CObject { }; // class CDealInfo : public CObject { }; // class CSymbolInfo : public CObject { }; // class CAccountInfo : public CObject { };
非常熟悉的语法,不是吗?我们只需要填写类的主体。为此,在 MetaEditor 中,我们为新的类创建一个文件 TradeExpert.mqh。将先前生成的代码复制到该文件。为了便于阅读,我们删除了针对 CTradeExpert 类的成员的重复出现的访问说明符 protected。
删除与标准库类的声明有关的行。之后,为每一个使用的标准库类添加文件包含指令 # Include,因为开发人员已经定义了这些类。再添加我们自己的备注。如此一来,我们得到以下代码:
//includes #include <Trade\Trade.mqh> #include <Trade\AccountInfo.mqh> #include <Trade\DealInfo.mqh> #include <Trade\SymbolInfo.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CTradeExpert { private: int Magic_No; // Expert Advisor magic protected: CTrade e_trade; // An object for executing trade orders CAccountInfo e_account; // An object for receiving account properties CDealInfo e_deal; // An object for receiving deal properties CSymbolInfo e_symbol; // An object for receiving symbol properties public: double e_pnt; // Value in points public: CTradeExpert() { } }; //+------------------------------------------------------------------+
现在,让我们向我们的 EA 交易类添加更多的交易函数模块。
这些函数可能包括:CheckSignal、OpenPosition、CheckPosition、ClosePosition 等。我希望您已经知道“条件服务”原则。在本例中,我们的测试类 CTradeExpert 对您而言似乎并没有困难。我特别专注于某些已被大众所熟悉的 EA 交易示例,从而让您更容易理解 UML 的机制。
现在,类的模型如图 8 所示。
图 8. 类 CTradeExpert 的 UML 模型
对于类的更新模型,我们也可以使用已经描述的方法生成代码。
2.3 活动图
使用此类 UML 图,我们能够使用数据流和控制流模型研究系统的行为。活动图是步进式活动和动作工作流的图形表示。
活动图与流程图不同,流程图仅描述算法步骤。活动图的概念更广。例如,它有可能指定其中的对象的状态。
开发人员使用活动图来描述:
假定创建的 EA 类 CTradeExpert 将在EA 交易文件 Test_TradeExpert.mq5 中使用。我们记得,在 MetaEditor 5 中创建 EA 时使用的默认模板提供三个默认事件处理函数:OnInit、OnDeinit 和 OnTick。让我们在此稍作展开。
让我们尝试用我们的 EA 操作帐户显示文件 Test_TradeExpert.mq5 的图。应在这里指出,EA 交易或其结构相当简单。现在我们仅是培训。因此,简单的 EA 结构就行了。
让我们为我们的 EA 交易的使用设计一个图,文件 Test_TradeExpert.mq5 包含了该程序的算法。
那么,所有一切都从初始节点开始(图 9)。一个控制令牌从这个节点移到调用动作“创建 EA 交易实例”的节点。此操作发起对象流(蓝色箭头),该流改变对象节点的状态 (myTE=created),并发起到一个节点的控制流,该节点调用动作“初始化 EA 交易”。
控制流以活动链的形式表示,活动链连接两个活动节点,并且只有控制令牌通过活动链传递。
对象流以活动链的形式表示,仅向其传递对象或数据令牌。
活动节点是用链连接的活动流中单个点的抽象类。
决策节点是一个在输出流之间做出选择的控制节点。
对象节点表示在活动中使用的对象。
活动链是两个活动节点之间的有向连接的抽象类。
初始节点显示活动在哪里开始。
终止节点是完成所有活动流的活动。它又更改对象 myTE 的状态 (myTE=initialized) 并将控制令牌传递给决策节点。如果 EA 交易成功初始化,则控制流前进到节点“处理交易事件 NewTick”。如果初始化失败,则控制令牌首先进入泛化节点,然后再进入动作节点“去初始化 EA 交易”。
令牌是抽象建构,是为了便于描述静态定义的活动图的动态执行过程而引入的。令牌不能包含任何附加信息(空令牌),在这种情况下,它被称为一个控制流令牌;或者它可以包含到一个对象或数据结构的引用,在这种情况下,它被称为一个数据流令牌。
让我们看一看来自决策节点的第一个控制流。它被导向一个具有中断动作的区域,如用红色虚线绘制的圆角矩形和构造型 "interruptible"(可中断)所示。当控制流处于此区域时,它可能意外停止。如果激活收到事件“卸载 EA 交易”的动作节点(橙色标记),则它将中断所有流程。控制令牌移到中断链(橙色锯齿箭头),然后前往连接节点。之后,EA 被去初始化。然后,控制令牌前往节点“删除全局变量”,然后流程将在活动完成节点内结束。
动作节点“去初始化 EA 交易”也通过对象流改变对象 myTE 的状态 (myTE=deinitsialized)。节点“删除全局变量”接着删除对象 myTE (myTE=deleted)。
图 9. Test_TradeExpert.mq5 的活动图
假定控制流是稳定的:不会卸载 EA。控制流从节点“处理交易事件 NewTick”移到另一块 - 扩展区域,该区域的构造型定义为 "iterative"(迭代)(绿色虚线矩形)。
我将此区域称为“交易块”,以反映基本特性并改善对图的理解。块的一个特征是针对输入对象循环执行操作。我们只需要 2 个循环 - 处理长仓和短仓。在块的入口端和出口端,分别有包含交易方向对象的扩展节点(长仓或短仓)。
扩展节点是进入或离开扩展区域的一组对象的集合,为每个对象运行一次。
发送信号的动作节点(发送信号动作)表示信号发送。
接收事件的动作节点(接收事件动作)等待接收相应类型的事件。
因此,此类节点分别按以下类型处理每个方向:“检查信号”(信号发送节点)、“接收信号”(信号接收节点)|“建仓”(信号发送节点)“检查仓位”(信号发送节点)、“平仓”(信号发送节点)。应该指出,方向对象可以在动作节点之间以对象流传递,如紫色箭头所示。只要 EA 被卸载,块内的操作就将继续。
2.4 序列图
我们使用序列图来描述对象互动序列。此类图的一个非常重要的方面是时间。
因此,图有两个隐含的刻度。水平刻度表示对象互动序列。垂直刻度表示时间轴。时间区间的起点是图的上部。
图的顶部包含互动的图对象。对象有其自己的生命线,以垂直的虚线表示。对象交换消息。以箭头表示。当某个对象处于活动状态时,它接收控制焦点。此焦点以图形方式表示为生命线上的狭窄矩形。
对象是包含用冒号分隔的有下划线的对象名称和类名(可选)的矩形。
对象生命线是显示对象在某个时间段内存在的线条;线条越长,对象的存在时间越长。
控制焦点表示为一个狭窄矩形,矩形的上端表示对象接收控制焦点的起点(活动开始),其下端表示控制焦点的终点(活动结束)。
在 UML 中,用一组消息表示每个互动,而对象参与消息的交换。
让我们做一些练习。
客户端是一个参与者。它发起 EA 交易的操作。标记为 "event"(事件)构造型的其他对象是客户端的事件:Init、Deinit、NewTick。当然,如果您愿意,可以扩展事件的范围。当启动 EA 交易时,在全局级别创建 myTE 对象。它是 CTradeExpert 类的一个实例。类对象比图中的其他对象稍低一些,表示它是在构造函数之后创建的。
用带有开放箭头的虚线标记一个创建命令,并含有消息 1.1 CTradeExpert()。带有箭头的虚线表示默认构造函数 CTradeExpert() 的“创建类型”。在创建 CTradeExpert 的一个实例之后,步骤 1.2 激活 - 控制焦点返回到客户端。为了便于阅读,我用 #.# 的形式表示同步消息,例如 1.1,用 # 的形式表示异步消息。然后,客户端在步骤 2.1 中使用 OnInit() 函数处理 Init 事件,焦点返回到步骤 2.2。“调用”类型消息显示为在端部带有“实心”三角箭头的线条。
如果 Init 事件向客户端返回一个不为零的值,则表示初始化失败:进行步骤 3.1,生成并处理 Deinit 事件。在步骤 3.2 中,控制焦点返回到客户端。然后删除 CTradeExpert 类对象(步骤 4.1)。顺便提一下,在创建类图时,我没有在类 CTradeExpert 中包含析构函数。这可以在以后进行。这是图表构建的优点之一,几个图表的构建过程是迭代的。可以首先完成一个图表的操作,然后再完成另一图表的操作,以后可以修改第一个图表。
应该指出,标准 EA 模板的 MQL5 代码不包含处理初始化失败的块。我已经将其列出来以节省逻辑顺序。UML 序列图使用 opt 块,该块含有警备条件 OnInit()!=0,相当于 MQL5 结构 if(OnInit()!= 0) {}。
在步骤 4.2 中,控制转移到客户端。
现在,客户端准备好处理事件 NewTick。
此事件的处理在表示无限循环的块 loop(循环)中进行。即 EA 将一直处理此事件,直到我们禁用它为止。客户端使用 OnTick 函数处理 NewTick 事件(步骤 5)。在步骤 6 中,控制焦点转移到 EA myTE。使用 4 个自返消息,它实施以下函数:CheckSignal、OpenPosition、CheckPosition、ClosePosition。之所以称为自返是因为 EA 对象向其本身发送消息。
此外,CTradeExpert 类的这些函数也包含在 loop(2) 块中。2 表示循环由两次遍历组成。为什么是 2 呢?因为它处理两个方向的交易 - 长仓和短仓(从步骤 7 至 10)。在步骤 11 中,焦点转移到客户端。
步骤 12 和 13 分别负责去初始化和删除 EA 对象。
图 10. Test_TradeExpert.mq5 的 SD 图
这样,我们获得了主要的设计技巧。在创建的图的帮助下,开发人员的工作得以优化。现在,我们可以开始为文件 Test_TradeExpert.mq5 编写代码。当然,您也可以在没有图的情况下进行。但是,当您有复杂的 EA 时,图的使用减少了出错的可能性,并让您能够高效地管理交易系统的开发。
现在,我们使用 EA 模板创建 Test_TradeExpert.mq5。
我们在全局层面创建 CTradeExpert 类的一个实例 myTE。
现在,让我们填写 OnTick() 函数的主体。
我们编写的类函数如下所示:
for(long dir=0;dir<2;dir++) { myTE.CheckSignal(dir); myTE.OpenPosition(dir); myTE.CheckPosition (dir); myTE.ClosePosition(dir); }
事件 NewTick 的处理与此类似。当然,我们仍然需要指定类的数据成员要使用的每个函数,还要指定其他事情。但是让我们将这些工作留待以后处理。现在,我们的目标是将 UML 图的逻辑转换为 MQL5 代码。
作为一个例子,让我们为一个复杂的 EA 交易创建图表。在 MQL5 中要实施指定策略,让我们在这一环境中定义其功能。一般而言,我们的 EA 交易将执行交易操作;具体而言,它将生成交易信号、维持未平仓位和进行资金管理。它相当于一个模板交易策略。然而,出于训练目的,我们将尝试处理这个 EA。
首先,我们为我们的 EA 创建一个用例图。它与我们前面讨论的稍有不同。我注意交易系统的内部环境,忽略外部(图 11),因为在代码中,我们将只实施交易任务。
图 11. 交易系统的用例图
现在,让我们定义 EA 的结构。假定我们将使用标准库进行开发,因为它与交易系统的规定目标一致。最近,它已经得到实质性扩展。并且更重要的,它涉及交易策略类。因此,我们的目标是创建一个类图。它并不是很简单,因此您需要耐心。
我愿意在这里指出我们考虑标准库有几个原因。首先,我们尝试以其为基础创建交易机器人。其次,也非常重要的一点,我们对使用 UML 图有一些实践。第三,或许库本身也很宝贵。因此,我们可以从库学到很多有用的东西,并且同时尝试理解其并不非常简单的结构。
UML 图的结构的代码转换称为逆向工程。事实上,我们手动进行此工作。有专业软件能够自动进行此工作(IBM Rational Rose、Visual Paradigm for UML 等)。但是出于实践目的,我认为我们需要“手动”工作。
让我们使用 "Class"(类)块,创建一个用于实施交易策略 CExpert 的基类的模型。让我们看一看在 CExpert 类主体中还使用了哪些类和结构。首先,应该指出,CExpert 类派生自基类 CExpertBase,该类又派生自基类 CObject。
在图中,我们创建这些类的块,使用带有“空心”三角箭头的线条(泛化)定义类之间的关系。向 CExpert 类的模型添加备注(黄色折角矩形)。现在,中间类结构看起来如图 12 所示。让我们将这个图称为 Expert。
图 12. Expert 图,初始视图
让我们看一看文件 Expert.mqh 中的代码。类 CExpert 与其他对象一样,涉及枚举 ENUM_TRADE_EVENTS 和 ENUM_TIMEFRAMES,8 个预定义结构 MqlDateTime 之一。该类还使用其他类实例,例如:CExpertTrade、CExpertSignal、CExpertMoney、CExpertTrailing、CIndicators、CPositiontInfo、COrderInfo。
现在,我们需要对图进行某些更改。首先,我们指定类 CExpertSignal、CExpertMoney、CExpertTrailing 派生自基类 CExpertBase,类 CPositiontInfo、COrderInfo 派生自 CObject(我已经将其构造型设置为 "metaclass")。
让我们用 "use" 构造型标记 CExpert 类块和其他类块之间的依存关系,并且不要忘记 MqlDateTime 结构和枚举。我们更改块的颜色风格,得到以下结构 - 图 13。
图 13. Expert 图,初始视图
然而,此结构并没有反映整个画面,因为存在很多被已经提及的类间接使用的类。它们是什么样的类呢?首先,CExpertTrade 类派生自 CTrade。后者是 CObject 的子类。
类 CExpertTrade 使用 ENUM_ORDER_TYPE_TIME 枚举,类 CSymbolInfo 和 CAccountInfo 也是 CObject 的子类。类 CTrade 还使用 CSymbolInfo 类的实例。让我们对图进行更改。现在,我们的图如下图 14 所示。
图 14. Expert 图,初始视图
再一次指出,该图是不完整的。例如,如果查看标准库文件 Trade.mqh,您将看到 CTrade 使用几个不同的结构、枚举和 CSymbolInfo 类。如果它们都显示在一张图中,则加载的太多了。这会让其难以理解。
为了解决这种困难,我在图上使用了一个包。它将相关的类、枚举、其他包等封装在一起。我将这个包与接口中的各个图表元素连接在一起。例如,包 CTrade 的类图可表示为下图 15。
图 15. 包 CTrade 的类图
包 CTrade 的类图显示了 CTrade 类与枚举和结构之间的依存关系。
与 CObject 基类和使用的 CSymbolInfo 类之间的关系是通过一个接口来实施的。
在靠近接口的地方有一个与类图关联的图标,该类图将 CTrade 包作为一个单独的元素包含在内。单击任何一个接口将自动显示原来的类图(图 16)。
图 16. 带有接口的 Expert 图
接口关联是橙色的。包 CTrade 旁边的类图图标指出移动此图的可能性。因此,使用封装,我们可以显著提高类图的可读性。
那么,让我们继续吧。类 CObject 使用指向其主体中相同类的实例的指针。因此,我们可以使用相对于自身的构造型 "use" 设置 CObject 块的依存关系。
让我们看一看 CExpertBase 类模型的块。依据头文件 ExpertBase.mqh 的第一行,我们可以说这个类使用各种类的多个实例和枚举。因此,对于类模型及其关系,创建包 CExpertBase 是合理的。
那么,首先,我们在包图中定义 CExpertBase 类模型。通过接口,我们显示了与基类 CObject 的关系,以及与类 CSymbolInfo 和 CAccountInfo 一起使用的关系。接着,使用类块和依存关系,我们指定 CExpertBase 类使用下面的类:CiOpen、CiHigh、CiLow、CiSpread、CiTime、CiTickVolume、CiRealVolume。
前四个类派生自 CPriceSeries,后四个类派生自 CSeries。此外,CSeries 类拥有子类 CPriceSeries ,后者又拥有子类 CArrayObj。我们记得,以前使用过这些继承关系。在图中,将它们表示为泛化关系。
不要忘记类 CExpertBase 在其主体中使用枚举,例如:ENUM_TYPE_TREND、ENUM_USED_SERIES、ENUM_INIT_PHASE、ENUM_TIMEFRAMES。最后一个枚举也被类 CPriceSeries 和类 CSeries 的子类使用。为了不丢失关系,并且让图更加清晰,让我们调整图的每个元素的样式。如此一来,我们得到下图(图 17)。
图 17. 包 CExpertBase 的类图
它仍然是不完整的,并且我们不得不做更多工作。它生成四个类,这些类继承 CPriceSeries 类,也使用 CDoubleBuffer 类。此外,四个类中的每一个都使用其自己的派生自 CDoubleBuffer 的缓存类。因此,COpen 使用 COpenBuffer 等。CDoubleBuffer 有一个基类 (CArrayDouble) 并使用 ENUM_TIMEFRAMES。
CArrayDouble 继承 CArray,使用指向其相同类的实例的指针和 ENUM_DATATYPE 枚举。类 COpenBuffer 和价格系列缓存类(CHighBuffer、CLowBuffer、CCloseBuffer)使用 ENUM_TIMEFRAMES 枚举。
继承 CSeries 类的四个类仅使用它们自己的缓存类(CSpreadBuffer、CTimeBuffer、CTickVolumeBuffer、CRealVolumeBuffer)。第一个缓存类 CSpreadBuffer 继承 CArrayInt,其他的缓存类继承 CArrayLong。最后两个类使用指向它们自己的类的实例的指针和 ENUM_DATATYPE 枚举,并派生自 CArray,后者又是 CObject 的子类。
类 CPriceSeries 及其子类使用类 CDoubleBuffer 和枚举 ENUM_TIMEFRAMES。
CSeries 使用枚举 ENUM_SERIES_INFO_INTEGER、ENUM_TIMEFRAMES。它继承 CArrayObj。后者继承 CArray,使用 ENUM_POINTER_TYPE、指向其自己的类和 CObject 类的实例的指针。如此一来,我们得到如图 18 所示的图。
图 18. 包 CExpertBase 的扩展类图
原来带有接口的 Expert 图,针对类和包 CExpert、CExpertBase、CSymbolInfo、CAccountInfo 和 CObject,如下图 19 所示。
图 19. 带有接口的 Expert 图
我还添加了 CExpertTrade 使用的枚举 ENUM_ORDER_TYPE 。为了便于阅读,我用不同的颜色标记关系组。
我们继续。我希望您能理解逻辑。图中类的模型可能与其他类和其他实体有很多关系。因此,我仅仅用一个包替换了基本图中的某些组合。
那么,让我们来研究 CSymbolInfo。如果查看 SymbolInfo.mqh 的代码,您会看到基类 CSymbolInfo 使用 MQL5 枚举和结构。对该类及其关系使用包是一个好主意(图 20)。
图 20. 包 CSymbolInfo 的类图
图中的某些空白区域可用于添加备注。我还标记了与父类 CObject 的关系接口。原来针对包和类的 Expert 图将稍做修改。我将在以后提供更新后的版本,届时将在图中反映出全部的类和包。
那么,让我们继续吧。让我们看一看 AccountInfo.mqh 中的MQL5 代码。如该代码所示,CAccountInfo 也使用某些枚举。我们在将为这个类及其与其他实体的关系创建的包图中反映它们(图 21)。
图 21. CAccountlInfo 包图
现在,让我们处理 CExpert 类。对于这个类,我们也创建一个包 CExpert,如图 22 所示。我们继续改善主图的可读性。类 CExpert 与其他几个类连接在一起,如带有箭头的橙色接口线所示。
图 22. CExpert 包图
让我们研究余下的其他类。我们将为它们创建更多的包。
CExpertSignal 派生自 CExpertBase。此关系已经在原来的图 Expert 中显示出来。此外,类 CExpertSignal 使用 CArrayObj、COrderInfo、CIndicators 及其自己的类的实例(图 23)。具体而言,与类 CArrayObj 的关系的接口会将我们带到 CExpertBase 包图,该图显示类 CArrayObj 与其他实体的关系。
图 23. CExpertSignal 包图
现在我没有显示所有的图 - 可以在附件 Expert.simp 中找到它们。现在,让我们看一看包和类的更新后的 Expert 图(图 24)。
如您所见,图中几乎所有关键的类都被封装到包中,从而让图更加容易理解。我将泛化线的颜色改为棕色,以将其从依存关系线中区分开来。
图 24. 包和类的 Expert 图
这样,我们已经反映了能够从标准库中的代码获得的用于创建图的所有一切。我们只需要添加某些指定 EA 交易的交易操作的块。
第一个块是 CmyExpert 块,从 CExpert 类继承交易“技能”。这是我们对其进行如此之长的逆向工程的块。它实施一个具体的交易策略。我们还需要指定 EA 的基类的虚函数。
为此,我们创建了类 CmyExpertSignal、CmyExpertMoney、CmyExpertTrailing 的块,并指出它们派生自相应的父类(图 25)。
图 25. 包和类的扩展 Expert 图
每个类应包含什么函数和数据由开发人员决定。在这里,我尝试显示更加广义的方案,而不是派生类的具体实施。因此,对于每个派生类,我们可以一个具有所含方法和属性的详细列表的单独的图,如已经完成的图 8 所示。
现在,让我们看一看如何在我们的工作中使用序列图。让我提醒您,它会显示我们的 EA 如何按时间线运行。
因此,我按时间顺序编写 EA 工作的详情(图 26)。
图 26. EA 的序列图
客户端是一个参与者。它在全局级别创建 myTrader 对象 - CmyExpert 的一个实例(步骤 1.1)。绿色表示客户端的预定义事件(Init、Deinit、NewTick、Trade)。前文已经说明了序列图逻辑。在这里,我愿意指出某些具体的事项。当 EA 交易的主体增大时,代码越来越多,变得更加难以在图中显示。
使用块方法来解决这个问题。以块的形式对一组某些常见函数进行可视化。作为一项原则,它是另一个序列图。据说是一种互动使用。
因此,在本例中,我创建了名为 OnInit 的序列图以反映在单独的图中处理客户端事件 Init 的逻辑。在语法上,它被定义为带有关键字 ref(reference)的边框,并在控制令牌从 OnInit(步骤 2.1)传递到 Init 对象的生命线时使用。
此外,我设置了一个移动到这个 OnInit 序列图的接口。也就是说,如果您双击边框,则可以打开详细的 OnInit 序列图(图 27)。
图 27. OnInit 的序列图
重复某些动作即可非常方便地移到其他序列图。
例如,OnInit 图包含与 EA 去初始化有关的动作,其处理在 myTrader_Deinit 中完成(图 28)。
图 28. myTrader_Deinit 的序列图
整体而言,在 EA 设计的这个阶段,我有四个序列图。很自然,在更加正式的开发中,您可能需要其他的图。例如,我没有处理客户端的其他事件(NewTick、Trade)。
在本文中,我建议使用图形语言 UML 考虑 EA 交易开发过程的多维性质,该语言用于面向对象软件系统的可视化建模。此方法的主要优点是实现了设计程序的可视化。
与任何复杂的现象一样,UML 有其自己的缺点,开发人员应注意到缺点(冗余、语义不精确等)。
我希望所描述的 EA 开发方法能让您产生兴趣。如有任何评论或建设性的批评,我将不甚感激。
文件位置:
# | 文件 | 路径 |
说明 |
---|---|---|---|
1 | TradeExpert.mqh | %MetaTrader%\MQL5\Include | Expert Advisor 类 |
2 | Test_TradeExpert.mq5 | %MetaTrader%\MQL5\Experts | EA 交易 |
3 | Expert.simp | %Documents%\UML projects | UML 项目图 |
4 | SoftwareIdeasModeler.4.103.zip | %Program Files%\SoftwareIdeasModeler | Software Ideas Modeler 分发文件 |
参考文献:
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程