百闻不如一见
对交易历史进行可视分析是交易者的分析工作的重要部分。如果不是这样,则不会有要将数字世界转换为图片世界的技术分析。显然,这是因为 80% 的人类感知是通过眼睛进行的。对信息进行概括的统计不能指出很多细微差别。只有用其对数字世界的直观感知进行可视化才能注意到细节。常言道:百闻不如一见。
在本文中,我们将不考虑如何编写一个旨在实现交易历史的可视化自动显示的 EA 交易。我们将讨论在对象之间传递信息、规划大型应用、管理图表、同步不同交易品种的信息等问题。
按照传统,首先我想告诉您播放器应用程序的优点及关联脚本,然后我们将进入代码分析。
在 MetaTrader 5 策略测试程序中运行成交
播放器的操作以 MetaTrader 5 HTML 报告为基础。因此,可以通过登录到必要的 ATC-2010 帐户并将交易历史保存为 HTML 报告来获得 2010 年自动交易锦标赛的历史记录。
因为 2008 年自动交易锦标赛的服务器已停用,我们不能以相同的方式获得历史记录。网站包含作为一个 ZIP 档案压缩的所有参赛者的综合报告。Automated_Trading_Championship_2008_All_Trades.zip
"Automated Trading Championship 2008 All Trades.zip" 档案应被解压到 MetaTrader 5 安装目录的 \Files 文件夹中。
要分析 2008 年自动交易锦标赛的历史数据,需要运行 Report Parser MT4 脚本,该脚本将解析历史数据,针对指定的登录帐户进行选择,并且将数据保存在一个二进制文件中。此二进制文件由 Player Report EA 交易读取。
Player Report EA 交易应通过指定的必要登录帐户,在策略测试程序中运行。一旦测试结束,以 HTML 格式保存报告。指定的登录帐户不影响测试结果,但是将作为输入参数 "login" 在报告中显示。这样能够进一步辨别报告。因为报告是由同一个 EA 交易创建的,建议为报告指定一个与默认名称不相同的名称。
Report Parser MT4 脚本也有输入参数 "login",您应在其中指定要查看其历史数据的参赛者的登录帐户。如果您不知道参赛者的登录帐户,但知道昵称,则将登录帐户设为 0(默认)值启动脚本。在这种情形中,脚本将不按登录帐户进行选择;它将只创建一个在其中以字母顺序列出所有登录帐户的 csv 文件。文件名为 "Automated Trading Championship 2008 All Trades_plus"。一旦您在此文件中找到必需的参赛者,则以指定的登录帐户再次运行脚本。
因此,Report Parser MT4 脚本和 Player Report EA 一起依据 MetaTrader 4 格式的交易历史数据创建标准的 MetaTrader 5 策略测试程序 html 报告。
Player Report EA 交易程序并不按照实际的执行方式执行交易,它只是按近似方式执行。原因包括不同的报价、报告中时间到分钟的取整以及执行期间的最大允许滑点数。在大多数情形中,差异只有几点,这出现在 10% 的交易中。但是这在策略测试程序中足以降低利润,例如从大约 17 万降至大约 16 万。所有一切取决于成交量及最大允许滑点数。
播放器的操作
如前文所述,播放器可用于在其他应用程序的配合下观看 2008 年自动交易锦标赛的交易历史,或直接观看 2010 年自动交易锦标赛。
此外,播放器支持所有 MetaTrader 5 报告,因此,您可以观看在策略测试程序中运行的任何 EA 交易的交易历史,或者未经测试程序进行格式化,但从 "Toolbox"(工具箱)窗口的 "History"(历史记录)选项卡另存为报告的手动交易的历史。
Player History Trades exp v5 EA 交易的参数:
使用 MetaTrader 5 策略测试程序的一份报告作为成交历史播放器的输入文件。报告文件的名称应指定为 Player History Trades exp v5 EA 的输入参数 "name of html file of the strategy tester report" (策略测试程序报告的 HTML 文件的名称)的值。启动播放器时,用户能够在输入变量 "start of history"(历史记录的开始时间)和 "end of history"(历史记录的结束时间)内指定播放的时间范围。
如果未设置这些变量,则播放器将采用从第一笔成交至最后一笔成交的交易历史。用于交易的交易品种的数量没有任何区别。仅考虑帐户中的第一笔成交和最后一笔成交的时间。
此外,用户能够设置要分析其图表的交易品种的名称。应作为 "list of required charts"(所需图表的列表)变量的枚举指定名称。对此变量的分析不区分大小写以及分隔符的类型。如果未设定变量,则打开在帐户中交易的所有交易品种。有时候会有很多交易品种。
例如,Manov 在其交易中使用了 12 个货币对。我建议设置的交易品种数量一次不超过四个。首先,这便于安排它们,其次,图表太多也降低了播放速度。因为每个交易品种是在一般循环中处理的,增加交易品种的数量会减慢价格变动的生成。
即使您指定一个未在交易中使用的交易品种,播放器也将运行。在这种情形下,图表将不显示任何成交,它将与其他图表一样。此外,它还附加了余额指标;然而,它将仅显示其任何类型的一般余额的历史。
我有意跳过 "Delete chart when deleting the EA"(删除 EA 时删除图表)参数的说明。它涉及 EA 交易的行为,而不是其管理。重点在于 EA 交易为其操作分析很多信息。我决定 EA 拥有的某些以文件形式出现的信息将会对分析很有用。EA 交易创建包含每个交易品种的交易操作的 csv 文件以及具有所有交易品种的同步余额的文件,这对检测多货币篮子中的一个交易品种非常有用。
同一变量也用于删除 EA 交易自动打开的图表。通常,EA 交易应在其操作结束时清理此工作区。但是,如果用户希望在没有 EA 控制的情况下精密分析图表,他们应在将 "delete charts when deleting the EA"(删除 EA 时删除图表)参数设置为 'false' 的情况下启动 EA。
以下参数并不是非常重要:
生成器的周期设置价格变动 (tick) 生成器的初始参数。在这里,术语 "tick" 并没有采用其经典含义;它指价格水平变动。在 EA 交易中,价格变动是依据柱的四个点生成的。"Period of the generator"(生成器的周期)参数设置生成器的初始状态。此外,您将能够在播放器运行时在其中更改此参数。
为什么我们不从 M1 开始生成其所有周期?为什么我们需要更改生成器的周期?问题在于较大时间框架的柱包含很多 M1 柱,因此我们可能需要加快生成的处理。这是为什么要实施周期更改可能性的原因。并没有在生成器中实施所有时间框架,仅实施了其中部分时间框架。将在以后描述在代码中更改时间框架的方法。
"font of the comments to deals(成交备注的字体)参数可能会有用处,例如在成交备注阻碍查看成交本身时。如果您将字号设置为 1,则备注看起来如一条细线,不会阻碍查看。这样,在您从工具提示中找出对象名称时,您就能在 "List of objects"(对象列表)选项卡中查看成交量和持仓量。
交易历史是用单独的成交绘制的,但是绘制的线条取决于仓位的类型。
使用 "color of buy operations"(买入操作的颜色)和 "color of sell operations"(卖出操作的颜色),您可以设置您需要的颜色。
在以上屏幕截图中,您可以看到仓位水平通常与成交水平不同。
但是利润是依据仓位水平计算的。因此我决定用趋势线显示仓位并使用垂直线连接仓位水平和成交水平。仓位水平附近的备注显示以下信息:
[deal volume|position volume]
如果成交类型与仓位类型不对应(例如存在部分平仓),则量显示有其他符号:
[<deal volume>|position volume]
在第一个地方,您可以看到成交量与显示在交易报告中的一样;持仓量依据仓位的上一状态和成交做出的更改进行计算。
"number of speeds"(速度步进量)参数控制降低播放速度的步进量。播放器以最大速度启动。此外,您可以在 "number of speeds"参数的值范围内降低或增大速度。因此,速度按钮和生成器的周期组成了一套完整的交易历史播放速度的管理工具。
最后一个参数为 "vert. size of the progress button"(进度按钮的垂直大小)。我为更喜欢进度条大按钮的用户设置了此参数。一般而言,我的目标是避免将图表隐藏在控件后面。这是为什么将 "vert. size of the progress button"(进度按钮的垂直大小)参数设置为 8 的原因。
现在,让我们进入播放器的控件。
速度通过向左和向右箭头来控制。控制模式取决于中间(方形)按钮的状态。在未按下状态,它更改速度;在按下状态,它更改生成器的周期。
控制余额指标的对象显示为一个完整的椭圆形,但实际上它是由两个远远超过其可见尺寸边界的大按钮组成。左边的按钮用于添加及从图表删除余额指标,右边的按钮控制数据内容。
在 "All"(全部)状态,显示帐户总余额的相关信息;"Sum"(汇总)状态用于显示指标运行所在的图表交易品种的余额采样。指标的控制是异步的,这意味着指标只能运行在一个图表上,而不能同时运行在另一个图表上。
控制余额指标的对象是图表同步的唯一例外;所有其他控制对象都是同步的。换言之,对某个交易品种做出的更改也自动应用到其他交易品种。
plays/stop(播放/停止)显示您按它时要立即执行的操作。在播放期间,显示两条细线,表示如果您按它,则会暂停播放。反之,在处于暂停状态时,显示一个三角形。因此,如果您按它,则播放器将开始运行。
进度栏由 100 个作为触发器的控制按钮组成 - 如果某个按钮被按下,则所有其他按钮都变为未被按下。因为有 100 个按钮,播放周期被分为 100 部分。如果柱的数量不能被 100 整除,则余下的被添加到最后一部分。这是为什么设置包括 "start of history"( 历史记录的开始时间)和 "end of history"(历史记录的结束时间)参数的原因。通过更改这些参数,您可以前往必要的历史时间范围。
通过按一个按钮,用户可以更改价格变动的内部生成器的日期,并前往零柱。如果未被按下,但是生成器的内部时间已经移出活动按钮以外,则播放器将执行对应的自行打开操作。
因此,“进度栏”对象既是进度指标,也是活动导航控件的指标。播放器的控制对象自动隐藏及展开到图表的中间;因此,如果您需要按进度栏中的某个按钮,请将图表扩大到整个屏幕。
现在,让我们讨论一下播放器管理的图表的行为。播放器执行所有图表的同步,但是这并不意味着比例、图表类型、颜色方案、零柱偏移等在主图表中执行的任何改变都会在其他图表上重复。
改变包括时间框架的改变。在这里,我们应注意,被播放器视为主图表的图表是在其中显示控件的图表,而不是具有蓝色活动线条的图表。通常情况下,它们是相同的图表,但并不始终都是这样。要激活一个图表,在图表区中单击该图表。
播放器的使用有一个特点。如果两个对象在同一字段中,则按钮不起作用。这是为什么有时标线穿过播放器区时,要按一个按钮,您需要切换到另一图表或更改图表的垂直比例的原因。
视频演示了 2010 年自动交易锦标赛的一名参赛者 Manov 的交易回放。为此,我使用 login=630165 和 password=MetaTrader 参数在客户端中连接到他的帐户。交易报告以文件名 ReportHistory-630165.html 保存在文件夹 terminal_data_folder\MQL5\Files 中。您可以下载作为压缩档案出现的这个文件,并且将其解压到指定文件夹。
现在,一切都应工作正常。
开始开发
要开发一个应用,您应有一个计划,该计划在您进行研究时转换为一个方框图,然后再转换为代码。但是项目本身开始得更早。任何项目的起点都是用户要求的应用属性。那么,交易历史播放器应有哪些属性?
前四个项目确定一般概念。其他属性确定方法实施的方向。
播放器操作的一般计划:
此外,对于依据 MetaTrader 4 报告在策略测试程序中进行的交易,还需要一个特殊的 EA 交易:
这是开始开发的一般方案,是一种要求规范。如果您已经有了,您可以自顶而下,从概念到功能实施来规划代码的编写。
不在本文展开,我将在以后进一步说明代码中最重要的部分。在阅读代码时,您不会遇到任何问题,因为添加了说明充分的注释。
订单与成交
目前,有两个交易概念。在 MetaTrader 4 中使用的旧概念以及用于实际竞标并在 MetaTrader 5 中使用的概念,也称为“净”概念。在“MetaTrader 5 中的订单、仓位和成交”一文中详细说明了它们之间的差异。
我将仅说明一个重要差异。在 MetaTrader 4 中,订单可表示为一个存储建仓时间、建仓价格和交易量的相关信息的容器。在容器的门打开时,它处于活动交易状态。一旦关闭了容器,则其中的所有信息都被移入历史记录。
在 MetaTrader 5 中,仓位用作此类容器。但是重大差异在于缺少仓位历史。只有订单与成交的公共历史记录。尽管历史记录包含恢复仓位记录所需的所有必要信息,您也需要用一点时间来重新组织思维。
您可以分别使用 ORDER_POSITION_ID 和 DEAL_POSITION_ID 标识符找出选择的订单或成交属于哪个仓位。因此,要将历史转换为适合 MetaTrader 5 的格式,我将 MetaTrader 4 的订单记录分为两种单独的交易 - 建仓和平仓交易。
HTML 解析器
对于不熟悉计算机行话的那些人而言,我将说明“解析”一词是什么意思。解析指对文本或任意顺序的词位(符号、单词、字节等)进行句法(文法或词汇)分析,检查输入文本与指定文法的对应,并构建一个解析树,您可以据此执行进一步的计算或转换。
在解析器中使用了两个大类 CTable 和 CHTML。CTable 类的使用在“MQL5 中的电子表格”一文中详细说明,因此我不再说明该类。
为了解析 HTML,我开发了 CHTML 类。依据我的初步想法,应撰写一篇文章来对其进行说明。但是对于撰写文章而言,这个类太简单了,因此我将对其进行简单的说明。
可以用术语“标记”来说明类的一般概念。一个标记可表示为具有包围的函数。例如 Tag(header,casket),其中 'header' 是标记的标题(通常在此处指定控制页面外观的标记变量),'casket' 是标记容器的内容。此类标记包含整个 HTML 语言。
类的一般结构可表示为对象的三阶段调用。CHTML 类的实例在其主体中创建所有可能标记的对象。标记的函数通过一个模板创建,它们仅在名称和两个标记的设置上有所不同。
一个标记确定 header 的存在,另一个确定 casket 的存在。这些标记允许用共同的结构表示所有标记。每个标记实例在其主体中创建 CTegs 类的一个实例。此类包含针对所有标记的公共方法,并且它执行在文档主体中搜索必要标记的主要操作。
因此,三阶段调用看起来如下所示:
h.td.base.casket
此表达式表示 'h' 对象通过 'td' 对象(<td header >casket</td> 标记的实例),再通过嵌套对象 'base'(CTegs 类的对象)调用 'casket' 变量的值。
类还包含搜索标记的方法,它们在以下公共方法中组合在一起:
h.td.Search(text,start);
该方法返回标记结束处的搜索点,并填写标记的 'header' 和 'casket' 变量。
没有使用在类中准备的其他函数,因此我将不会说明它们,还有很多其他有趣的东西要讨论。
在使用 HTML 文档的说明结束之时,我想指出,在本文中使用了两类解析器;它们仅在保存从文件获取信息的类型方面有所不同。第一种使用将整个文档保存到一个类型为 'string' 的单一变量中。播放器使用这种方法。第二种对报告进行逐行解析。它在用于准备 2008 年锦标赛历史的脚本中使用。
为什么我要使用两种方法?问题在于要让 CTegs 类的函数能够正确运行,整个标记应放在分析后的字符串。然而并不是始终都能如此。例如,在 table、html、body 等标记中(它们是多行的)。字符串类型的变量允许存储 32750 个符号(依据我的计算),不包括制表符。通过 '\r'(在每 32748 个字符之后),我成功存储多达 2 000 000 个符号;在达到此值之后,我停止了我的尝试。很有可能还能存储更多符号。
那么,为什么我要使用两种方法?重点在于,对于播放器的通用解析器,您需要找到正确的表格。测试程序报告和成交历史报告的所需表格位于不同的地方。为了保持多功能性(从而让解析器能够同时理解两种报告),我采用的方案是在表格中搜索含有 "deals" 的 'td' 标记。
2008 年锦标赛的报告结构是已知的,无需搜索必需的表格。然而,报告文档非常巨大 (35 MB),将整个报告存放在一个变量中要消耗大量时间。这种情形要求采用第二种方法进行解析。
播放器
在“开始开发”一节中说明了对播放器的 10 个要求。因为多货币是第一位的,所以 EA 交易应管理图表。每个图表由一个具有让播放器工作所需的所有功能的单独对象来处理是符合逻辑的。
因为我们处理历史数据,因此我们需要单独的历史数据样本来进行无中断的操作,而不是指望我们能够在我们希望的任何时间获得历史数据。此外,与将其保存在播放器中相比,重复获取相同的历史数据也是浪费。最后,出台了以下方案:
面向对象编程 (OOP) 允许使用模块系统编写非常大型的应用。开发出来的 EA 交易程序代码部分可以是以前用脚本编写的,经过很少的修改即可在调试后连接到 EA 交易程序。
此类开发方案非常方便,因为您确保连接的代码不包含错误(因为它在脚本中运行时不出现错误),并且找到的任何问题都是修改错误。当您在一个地方作为一个程序描述一切时,从下到上编写代码则没有此类优点。并且新的问题可能出现在应用程序的任何地方。
因此,自顶而下的编程在编写应用程序的简单性和速度方面具有优势。您可能要问“这里有什么简单的?”,我会用一则寓言来回答 - 学骑自行车很困难,但是一旦学会了,则甚至不会注意到学的过程。您只会享受快速骑行带来的快乐。一旦学会了 OOP 语法,您将获得巨大的优势。
为了继续叙述,我需要说明三个 OOP 术语:“关联”、“聚合”与“组合”。
因为关联包括聚合与组合,在详细分析期间,不能作为聚合或组合说明的所有情形都被称为关联。一般而言,所有三个习惯用语都被称为关联。
class Base { public: Base(void){}; ~Base(void){}; int a; }; //+------------------------------------------------------------------+ class A_Association { public: A_Association(void){}; ~A_Association(void){}; void Association(Base *a_){}; // At association, data of the bound object // will be available through the object pointer only in the method, // where the pointer is passed. }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class A_Aggregation { Base *a; public: A_Aggregation(void){}; ~A_Aggregation(void){}; void Aggregation(Base *a_){a=a_;}; // At aggregation, data of the bound object // will be available through the object pointer in any method of the class. }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class A_Composition { Base *a; public: A_Composition(void){ a=new Base;}; ~A_Composition(void){delete a;}; // At composition, the object becomes the class member. };
在 MQL5 中有一个用于通过一个参数传递指针的函数:
GetPointer(pointer)
其参数为对象指针。
例如:
void OnStart() { Base a; A_Association b; b.Association(GetPointer(a)); }
在我的代码的 OnInit() 中调用的函数经常使用关联。组合在 CHTML 类中应用。同时使用聚合与组合来绑定 CPlayer 类中的对象。例如,使用聚合,CChartData 和 SBase 类的对象为在播放器中使用组合创建的所有对象创建一个公共数据字段。
可以通过图形方式表示如下:
其对象在 CPlayer 类中以组合方式创建的类具有一个能够在以后扩展功能的模板结构。模板的使用在“使用伪模板作为 C++ 模板的代替”一文中介绍,因此我不在这里详细说明。
类的模板看起来如下所示:
//this_is_the_start_point //+******************************************************************+ class _XXX_ { private: long chart_id; string name; SBase *s; CChartData *d; public: bool state; _XXX_(){state=0;}; ~_XXX_(){}; void Create(long Chart_id, SBase *base, CChartData *data); void Update(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void _XXX_::Create(long Chart_id, SBase *base, CChartData *data) { chart_id=Chart_id; s=base; // binding data to the player structure d=data; // binding data to the chart structure name=" "+ChartSymbol(chart_id); if(ObjectFind(chart_id,name)<0)// if there is no object yet {//--- try to create the object if(ObjectCreate(chart_id,name,OBJ_TREND,0,0,0,0,0)) {//--- } else {//--- failed to create the object, tell about it Print("Failed to create the object"+name+". Error code ",GetLastError()); ResetLastError(); } } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void _XXX_::Update() { }; //+******************************************************************+ //this_is_the_end_point
目前,我按模板创建了空的类,连接它们,检查它们是否正确处理了所有请求,之后才开始用必要的功能填写组合类。这就是所谓的自顶而下的编程。如果出现任何错误,您知道在哪里寻找其原因。
现在,开发的一般概念清楚了,我们能够继续进行细节方面的工作。
首先,让我们看一看在 Player History Trades exp v5 EA 交易中声明的函数的运行。
OnInit() 函数通常用于准备信息。它创建 CParser_Tester 类的一个对象,该对象解析策略测试程序的报告,获取所有交易金融工具的列表,处理成交,计算持仓量和仓位水平,然后在图表上绘制历史。最后一项说明为什么对象在传递数据之后没有被立即删除的原因。问题在于信息是在打开图表之前准备好的。并且图形对象需要一个图表 ID 来绘图。这是为什么在以后删除 CParser_Tester 类的对象的原因。
此外,因为我们拥有用于交易的交易品种的名称,Balance_Process() 函数被调用,它依据 M1 历史数据计算传递给它的所有交易品种的余额和资产净值以及总的余额和资产净值。
在这个部分中,应用对信息的缺乏尤其敏感;这是为什么我实施了在未下载其中一个交易品种的信息时中断 EA 运行的原因。当应用停止运行时,它显示一则提醒,指出需要为其下载信息的交易品种。
Balance_Process() 函数的运行结果是基于 M1 的余额和资产净值历史的二进制文件,而 M1 可以通过余额指标进一步分割为必要的周期。我稍微有一点超前,余额指标的运行将在以后说明。
启动 EA 交易的下一步是交易品种的选择。在这个地方,我们分析输入参数 "list of required charts"(所需图表的列表);如果必需的交易品种在 "Market Watch"(市场报价)列表中,将其添加到交易品种数列中。通过这种方式,我们使自己不会成为“傻瓜”,因为用户可能随便输入什么而不是交易品种的名称,并且用户也有可能拼错名称。
在我们拥有经过验证的用户请求打开的交易品种列表之后,我们可以打开图表。使用以下函数来进行:
ChartOpen(symbol,period)
此函数打开一个图表,其中含有通过参数传递给它的交易品种和周期。在理论上,此函数返回打开图表的 ID,但并不总是这样。
作为缺乏 ID 的结果,应用程序会出现错误。为了避免这种情况,我创建了两个函数:
ChartTotal(arrayID); // get the list of charts before opening additional charts CurrentChart(arrayID); // get the list of chart for operation
一个函数在打开图表之前执行,另一个在打开图表之后执行。ChartTotal() 函数获取在 EA 启动之前已经打开的图表的列表(包括 EA 运行所在的图表),并将它们的 ID 保存在输入数组中。
CurrentChart() 函数获取该信息,考虑到已经打开的图表创建一个新列表;然后依据列表差异,它将 EA 创造的图表的 ID 传递给参数数组。此方案是可靠的,因为它是依据图表打开的事实工作的。
现在,因为我们拥有了必需图表的 ID,我们可以控制它们了。为此,在一个循环中遍历所有图表,并且使用 CParser_Tester 的对象(您想必记得,我先前说过我们会需要它)绘制成交历史并创建用于管理图表的对象。
OnInit() 中的最后一个操作 - 创建计时器并调用其来工作。所有其他操作都将在 OnTimer() 中执行。
创建播放器的第一个问题出现在开发的初始阶段。该问题是计时器的创建问题。EventSetTimer(timer) 函数允许创建频率不小于 1 秒的计时器。通过此类型,会每秒生成一个价格变动。即使考虑到人类视觉的限制,一秒钟也太长了。我需要至少 100 毫秒。
这是为什么我在计时器内实施一个循环的原因;它在新的计时器事件发生之前几毫秒退出。但是这种实施使很多技术解决方案变为不可能。例如,接收事件的可能性消失了,因为它们不断地等待计时器从循环中退出。并且接收事件的不可能性也让将播放器对象放在指标中并同时执行所有图表的并行计算的可能性消失不见。但是对于图表的后续处理,EA 交易变得非常快速。
图表激活事情被组合类 CClick 代替,其对象创建在 Click(n) 函数的循环中处理的活动图表改变的信号。Click() 函数是跟踪图表激活按钮的改变的触发器。如果它检测到按钮被按下,则它将所有其他对象切换到无反应状态。图表激活按钮始终接近用户,但它是不可见的,因为其大小为整个图表,其颜色是背景色,并且在底层。当一个图表被激活时,按钮被移到图表的可见边框后面,这样允许查看播放器控件的图形对象,而在无反应模式下,图形对象被图表激活按钮所遮挡。
此外,在我们使用 Click() 函数检测到主图表时,开始计算时间运行,调用活动播放器的 Progress(Time) 函数。此函数执行以下计算:检查用户是否执行了导航操作 - 如果没有,检查是否为前往下一根柱的时间;如果是,则检查是否应将进度移到下一部分。
最后,在我们退出 Progress(Time) 函数时,循环拥有有关当前时间的信息,此信息对以后的计算非常有用。然后,活动图表的设置被复制到从属图表。这通过 CopyPlayer(n) 函数中的一个循环来进行。之后,在 Play(Time) 函数中开始执行应对图表做出的所有变更,让用户认为时间在变动、报价在出现,交易在执行。
播放器的组合类。
* - 类不包括图形对象。
如我已经提到的,CChartData 和 SBase 类的对象使用聚合为播放器中的所有对象创建一个公共数据字段。CChartData 类的对象用于存储和更新有关图表的信息及管理图表。所谓管理图表指通过复制主图表的设置来更改其设置。这是执行图表同步的方式。用户只需要通过更改活动图表的设置发出初始信号,之后播放器的几个函数承担余下的同步操作。
以下说明进行方式:
在 EA 交易中定义的 CopyPlayer(n) 函数在一个循环中调用 CPlayer::Copy(CPlayer *base) 函数,以关联方式将指针传递给活动图表的播放器。在 CPlayer::Copy(CPlayer *base) 中,活动播放器的 CChartData 对象的指针是以关联方式从播放器指针传递的。因此,有关活动图表状态的信息放在从属图表的 CChartData 类的对象中以供复制。之后,在 CPlayer::Update() 函数中更新信息,在该函数中执行所有必要的检查,并且所有对象都被切换到必要的状态。
我在前面答应过要告诉您如何在生成器的可用周期列表中添加周期。为此,打开 "Player5_1.mqh" 包含文件。在文件的开头声明了静态数组 TFarray[]。应将必需的周期添加到填充数组的枚举中的对应位置,并且不要忘记更改数组和 CountTF 变量的大小。之后,编译 Player History Trades exp v5 EA 交易程序。
余额图和亏损图
余额指标从 CPlay 类的对象管理。它包含控制方法和按钮。
指标的控制方法如下:
Ind_Balance_Create(); // add the indicator IndicatorDelete(int ind_total); // delete the indicator EventIndicators(bool &prev_state); // send an event to the indicator StateIndicators(); // state of the indicator, state checks
添加/删除方法取决于 name_on_balance 按钮的状态。它们使用标准 MQL5 函数 IndicatorCreate() 和 ChartIndicatorDelete()。
指标接收事件并依据事件代码执行指标的 OnChartEvent() 函数内的计算。事件分为三类。
它们是“更新指标”、“计算总余额”和“计算某个交易品种的余额”。因此,当发送事件时,视 name_all_balance 按钮的状态而定,用户控制计算的类型。但是指标代码本身不包含对交易历史的任何解析、仓位的计算或利润的重新计算。指标不需要这些。
余额指标旨在显示历史数据,因此,每次更改类型或添加/删除指标时没有必要重做所有事情。指标读取为 M1 时间框架计算的数据的二进制文件,然后,依据图表的当前时间框架拆分数据。
二进制文件由在 OnInit() 中调用的 Balance_Process() 函数准备。如果用户添加了一个未用于交易的交易品种,并且没有对应的二进制文件,则指标将显示两种类型的总余额的历史。
现在,让我们讨论一下传递给指标的数据的格式。要正确拆分信息,知道柱的四个点(开盘价、最高价、最低价和收盘价)并不足够。
此外,您还需要知道什么是第一位的 - 最高价或最低价。为了恢复信息,Balance_Process() 函数使用与策略测试程序的“1 分钟 OHLC”模式相同的原则 - 如果柱的收盘价低于开盘价,则第二点是最高价,否则为最低价。
相同的方案也用于第三点。结果,我们得到数据格式(开盘价、第二点、第三点、收盘价),其中所有一切都是一致和明确的。此格式用于拆分报价的 M1 历史。视解析后的交易历史而定,将结果用于计算余额和资产净值的历史(采用相同的格式)。
总结
在总结中,我想说此开发并不是要成为测试程序的可视化工具,尽管我们可以用这种方式来使用它。但是,如果在其中实施的想法对实际的可视化工具有用,我会非常高兴。播放器的开发旨在帮助交易者和 EA 编写者准备即将来临的锦标赛,并且有助于减轻分析交易策略的艰苦工作。
此外,我还想说 MQL5 语言是一种强大的编程工具,允许实施非常大型的项目。如果您仍然在阅读本文,则您可能已经注意到“播放器”项目包含大约 8000 行代码。我无法想象用 MQL4 编写此类代码会是什么样的情形,问题并不在于用程序对其进行描述。如果有一个准备就绪的开发,则可以按程序风格来重做。但是从头开发此类项目真的很艰苦。
祝您好运!
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程