内容目录
- 什么是分布图表?
- 快速开始
- 1. 从交易历史中重建仓位
- 1.1. 仓位反转
- 1.2. 计算仓位盈利
- 1.3. 开仓时间
- 1.4. 仓位重建的中间存储
- 2. 谷歌图表
- 2.1. 直方图 ('bar' 类型)
- 2.2. 饼状图 ('corechart' 类型)
- 2.3. 直方图 ('bar' 类型) + 饼状图 ('corechart' 类型) + 直方图 ('bar' 类型)
- 3. 从终端 (当前交易账户) 运行分析图表
- 4. 从策略测试器运行分析图表
- 波澜不惊的 "平静" 优化
- 变化
什么是分布图表?
在开发一个新的交易策略期间, 无法知道它原来是如何成功的。一款交易机器人几乎总包括输入参数, 底层规则将会用于入场信号的产生。在此情况下, 交易机器人编码之后, 它依然要简单地依赖策略测试器来找到那些表现良好的回测结果的输入参数组合。
不过, 本文提供了一种看上去略有不同的创造交易机器人的过程。在运行输入参数优化之前, 您可以简单地根据入场时间查看盈利和亏损分布。毕竟, 许多策略在入场时都存在 "有利" 和 "不利" 时刻。本文研究的是根据开仓时间绘制仓位的盈利分布图 (请注意, 不是成交, 而是具体仓位!)。研究这些图表后, 将有可能从稍微不同的角度审视策略。
图表通过调用 Google 图表 绘制。为了直观表现它们, 选择了 HTML。在网页上的示意图, 分布图表如下表:
图例. 1. HTML 报告的外观
前两行是整个交易帐户的汇总统计, 随后的几行统计表示每个品种按小时, 天和月份的入场项。
快速开始
文件 "DistributionOfProfits.mqh" 应放置于数据目录, 位于 ...\MQL5\Include\ 文件夹。脚本 "test_report.mq5" 则进行调用, 从指定的 "start" 日期开始绘制分析图表:
//+------------------------------------------------------------------+ //| test_report.mq5 | //| 版权所有 2016, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2016, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- input datetime start=D'2010.05.11 12:05:00'; #include <DistributionOfProfits.mqh> //--- CDistributionOfProfits Analysis; //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- Analysis.AnalysisTradingHistory(start); Analysis.ShowDistributionOfProfits(); } //+------------------------------------------------------------------+
1. 从交易历史中重建仓位
什么是仓位重建?事实上这是了解仓位重要参数的唯一方式, 如 盈利 和 其开仓时间。定义条款:
仓位利润 — 给定仓位的所有成交总利润;
开仓时间 — 在此仓位里首笔订单的开单时间。
终端包含的交易历史分为订单和成交历史。但我们需要的是仓位的历史。为此, 打开交易历史并关注成交的 DEAL_POSITION_ID 属性以及订单的 ORDER_POSITION_ID 属性。之所以选择这些属性的原因如下。事实上, 每个仓位均有唯一的标识符 (POSITION_IDENTIFIER), 其在每笔已开、修改过、已平的订单 (ORDER_POSITION_ID) 和成交 (DEAL_POSITION_ID) 里指定:
图例. 2. POSITION_IDENTIFIER, DEAL_POSITION_ID 和 ORDER_POSITION_ID 的联系
换言之, 如果来自交易历史的单独成交带有相同的 DEAL_POSITION_ID, 则可以保证仓位的重建。仓位反转的形势在此值得一提。来自文档的 仓位标识符:
仓位反转会将仓位的标识符改为导致反转的订单号。
1.1. 仓位反转
意即, 如果仓位在反转之后改变其标识符, 这将是一个不同的仓位。在此情况下是如何为成交分配标识符的?导致反转的成交是属于之前的,亦或反转的 (新的) 仓位?回答这个问题的一个简单例子 — 脚本 position_reversal_v1.mq5 已经编写好。
这个脚本执行三笔交易动作:
- 买入 0.01 — 开仓
- 卖出 0.02 — 反转仓位
- 平仓
在脚本的输入参数里应指定开始时间。此参数将被用于请求交易历史。在此例中: 脚本在 2016.09.05 10:32:59 运行, 输入参数与选择的 D'2016.09.05 10:32:00' 值有一点小缺口。
每个动作之后, 脚本打印仓位和其识别符, 以及成交历史和成交的 DEAL_POSITION_ID。此处是打印结果:
10:32:59.487 position_reversal_v1 (EURUSD,M3) Buy 0.01, "EURUSD" 10:33:00.156 position_reversal_v1 (EURUSD,M3) Position EURUSD POSITION_IDENTIFIER #96633525 10:33:00.156 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00 10:33:00.156 position_reversal_v1 (EURUSD,M3) 10:33:06.187 position_reversal_v1 (EURUSD,M3) Sell 0.02, "EURUSD" 10:33:06.871 position_reversal_v1 (EURUSD,M3) Position EURUSD POSITION_IDENTIFIER #96633564 10:33:06.871 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00 10:33:06.871 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06 10:33:06.871 position_reversal_v1 (EURUSD,M3) 10:33:12.924 position_reversal_v1 (EURUSD,M3) PositionClose, "EURUSD" 10:33:13.593 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00 10:33:13.593 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06 10:33:13.593 position_reversal_v1 (EURUSD,M3) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633564 profit -0.10 10:33:13.593 position_reversal_v1 (EURUSD,M3)
第一个交易操作 — 买入 0.01。其 POSITION_IDENTIFIER 等于 96633525。交易历史包括一笔成交 — 它所分配的 DEAL_POSITION_ID 是 96633525。对于这笔成交, DEAL_POSITION_ID 与仓位的 POSITION_IDENTIFIER 相匹配, 意即这笔成交属于此仓位。
第二个操作 — 卖出 0.02。这次操作导致仓位反转: 已经有 '买入 0.01' 的仓位, 那么脚本执行 '卖出 0.02', 其结果是剩有 '卖出 0.01' 的仓位。当前仓位的 POSITION_IDENTIFIER 已被改变为 96633564。意即得到了新仓位。在这一时刻, 在成交历史中发生了什么事情?交易历史现在包含两笔成交, 都具有相同的 DEAL_POSITION_ID, 等于 96633525。与此同时, 第二笔成交有 "盈利 -0.06"。
所以, 导致仓位反转的成交属于之前的仓位。
第三个交易操作 — 买入 0.01。在此阶段, 持仓不再存在 (因为它已经被平仓), 交易历史包括三笔成交: 第一和第二笔有相同的 DEAL_POSITION_ID 等于 96633525, 但这个标识符在第三笔成交中已被改变为 "96633564", 且加上了 "盈利 -0.10"。就是如此, 第三笔成交属于第二个仓位, 结果来自第一个仓位的反转。
1.2. 计算仓位盈利
基于在章节 1.1 提供的信息, 能够为每个重建的仓位精确地确定利润计算的算法。具有相同 DEAL_POSITION_ID 的所有成交, 其账务总结果将通过计算仓位盈利得到, 即 POSITION_IDENTIFIER 等于 DEAL_POSITION_ID。而作为唯一与买卖相关的交易操作, 有必要对交易类型引进限制。只有以下来自 ENUM_DEAL_TYPE 枚举的成交可以被挑选:
ENUM_DEAL_TYPE
ID | 描述 |
DEAL_TYPE_BUY | 买入 |
DEAL_TYPE_SELL | 卖出 |
第一个脚本略加修改并以 position_reversal_v2.mq5 为名保存。脚本 position_reversal_v2.mq5 依然包含三个交易块 — 买入 0.01, 卖出 0.02, 平仓。脚本中的新颖之处在于 PrintProfitPositions() 函数: 它计算重建仓位的盈利。让我们来更详细地查看此函数:
//+------------------------------------------------------------------+ //| 打印仓位盈利 | //+------------------------------------------------------------------+ void PrintProfitPositions(void) { //--- 仓位盈利结构; struct struct_positions { long position_id; double position_profit; //--- 构造器 struct_positions() {position_id=0; position_profit=0.0;} }; struct_positions arr_struct_positions[]; //--- 请求交易历史 HistorySelect(start,TimeCurrent()); uint total =HistoryDealsTotal(); ulong ticket =0; long deal_id =0; double profit =0; long type =0; //--- 对于所有成交 for(uint i=0;i<total;i++) { //--- 尝试得到成交单号 if((ticket=HistoryDealGetTicket(i))>0) { //--- 得到成交属性 deal_id =HistoryDealGetInteger(ticket,DEAL_POSITION_ID); profit =HistoryDealGetDouble(ticket,DEAL_PROFIT); type =HistoryDealGetInteger(ticket,DEAL_TYPE); //--- 只有买入或卖出 if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL) { bool seach=false; int number=ArraySize(arr_struct_positions); for(int j=0;j<number;j++) { if(arr_struct_positions[j].position_id==deal_id) { arr_struct_positions[j].position_profit+=profit; seach=true; break; } } if(!seach) { ArrayResize(arr_struct_positions,number+1); arr_struct_positions[number].position_id=deal_id; arr_struct_positions[number].position_profit+=profit; } } } } //--- int number=ArraySize(arr_struct_positions); for(int i=0;i<number;i++) { Print("id ",arr_struct_positions[i].position_id," 盈利 ",arr_struct_positions[i].position_profit); } }
首先, 声明 struct_positions 结构:
//--- 仓位盈利结构; struct struct_positions { long id; double profit; //--- 构造器 struct_positions() {id=0; profit=0.0;} }; struct_positions arr_struct_positions[];
结构 struct_positions 包括两个字段:
id — 仓位标识符;
profit — 仓位盈利。
然后声明 arr_struct_positions[] 结构数组。
以下是调用成交历史的辅助变量模块:
//--- 请求交易历史 HistorySelect(start,TimeCurrent()); uint total =HistoryDealsTotal(); ulong ticket =0; long deal_id =0; double profit =0; long type =0;
以下循环调用成交历史:
//--- 对于所有成交 for(uint i=0;i<total;i++) { //--- 尝试得到成交单号 if((ticket=HistoryDealGetTicket(i))>0) { ... //--- 只有买入或卖出 if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL) { ... } } }
与此同时, 如上所言, 不要忘记仅关注 "买入" 和 "卖出" 成交。
此处的代码遍历所有 arr_struct_positions[] 结构。如果它发现 position_id 字段和来自成交历史的成交 DEAL_POSITION_ID 匹配, 则 累计当前成交利润 并保存到结构数组相应索引的位置。
bool seach=false; int number=ArraySize(arr_struct_positions); for(int j=0;j<number;j++) { if(arr_struct_positions[j].id==deal_id) { arr_struct_positions[j].profit+=profit; seach=true; break; } } if(!seach) { ArrayResize(arr_struct_positions,number+1); arr_struct_positions[number].id=deal_id; arr_struct_positions[number].profit+=profit; }
如果结构数组里没有相匹配的 id 和成交 DEAL_POSITION_ID, 则结构数组递增一个元素, 新元素将立即填充数值。
结构数组填充后, 代码遍历所有数组, 打印仓位标识符和利润:
//--- int number=ArraySize(arr_struct_positions); for(int i=0;i<number;i++) { Print("id ",arr_struct_positions[i].id," 盈利 ",arr_struct_positions[i].profit); }
1.3. 开仓时间
在脚本 position_reversal_v1.mq5 里加入新代码 — 输出所有订单来自交易历史的参数。脚本保存为 position_reversal_v3.mq5。
2016.09.06 15:05:34.399 position_reversal_v3 (USDJPY,M1) Buy 0.01, "EURUSD" 2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1) Position EURUSD POSITION_IDENTIFIER #96803513 2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00 2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513 2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1) 2016.09.06 15:05:41.088 position_reversal_v3 (USDJPY,M1) Sell 0.02, "EURUSD" 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) Position EURUSD POSITION_IDENTIFIER #96803543 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543 2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1) 2016.09.06 15:05:47.785 position_reversal_v3 (USDJPY,M1) PositionClose, "EURUSD" 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803543 profit -0.05 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543 2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1) Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803561
脚本可直观地诠释来自帮助的说明:
仓位标识符是分配给每一笔重开仓位的唯一数字。在它的存活周期里不能被改变与其 相应的用于开仓的订单号。
所以, 为了确定开仓时间, 在历史中搜索订单号 (ORDER_TICKET) 等于仓位标识符 (POSITION_IDENTIFIER) 并得到所发现订单的时间 (ORDER_TIME_DONE) 就足以了。
1.4. 仓位重建的中间存储
重建的仓位将保存在 struct_positions 结构数组里:
struct struct_positions { long id; datetime time; double loss; double profit; string symbol_name; //--- 构造器 struct_positions() {id=0; time=0; loss=0.0; profit=0.0; symbol_name=NULL;} };
where
id — 仓位标识符;
time — 开仓时间;
loss — 仓位的亏损, 此字段内的数据写入时会加上 "+" 符号 (这是为了在图表上有更好的视觉表现)
profit — 仓位盈利
symbol_name — 品种名称, 及仓位所属品种。
最后, 当绘制不同的图表时, 结构数组 struct_positions 将会用作交易历史合并后仓位的数据库。
2. 谷歌图表
谷歌图表服务将被用于显示分析。为做到这一点, 图表将被置于一个 HTML 页面内。之后, 页面会在操作系统默认设置的浏览器中 (使用 WinAPI 的 ShellExecute 函数) 打开。
在本文中使用两种图表类型: 直方图和饼状图。让我们来详细检验它们。
2.1. 直方图 ('bar' 类型)
'bar' 类型可以在 HTML 页面上显示以下图表:
此处是第一个图表的代码。将它保存为 *.html 扩展名的文件 (或下载本文末尾的 bar.html 文件), 并在浏览器里打开此文件:
<html> <head> <!--加载 AJAX API--> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> google.charts.load('current', {'packages':['bar']}); google.charts.setOnLoadCallback(drawChart1); function drawChart1() { var data1 = google.visualization.arrayToDataTable([ ['品种', '盈利', '亏损'], ['Si-6.16', 82.00, 944.00], ['Si-9.16', 56.00, 11.00], ['SBRF-9.16', 546.00, 189.00], ]); var options1 = { chart: { title: '品种的盈利/亏损', subtitle: '合计', }, bars: 'vertical', vAxis: {format: 'decimal'}, width: 440, height: 400, colors: ['#5b9bd5', '#ed7d31', '#7570b3'] }; var chart = new google.charts.Bar(document.getElementById('chart_div1')); chart.draw(data1, options1); } </script> </head> <body> <!--Div 将容纳饼状图表--> <div id="chart_div1"></div> <br/> </body> </html>
为了使用谷歌图表, 必须遵循以下描述的代码放置规则。
加载器和库文件应包含在 <head> 代码块里:
<!--加载 AJAX API--> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript">
此后, 指定图表类型和绘图用的函数及数据 (drawChart):
google.charts.load('current', {'packages':['bar']}); google.charts.setOnLoadCallback(drawChart1);
函数 drawChart1() 本身包括三个块:
- "var data1" — 数据块,作为图表绘图的基础
- "var options1" — 选项块, 指定图表参数
- 指向容器 的版块, 用于显示图表:
function drawChart1() { var data1 = google.visualization.arrayToDataTable([ ['品种', '盈利', '亏损'], ['Si-6.16', 82.00, 944.00], ['Si-9.16', 56.00, 11.00], ['SBRF-9.16', 546.00, 189.00], ]); var options1 = { chart: { title: '品种的盈利/亏损', subtitle: '合计', }, bars: 'vertical', vAxis: {format: 'decimal'}, width: 440, height: 400, colors: ['#5b9bd5', '#ed7d31', '#7570b3'] }; var chart = new google.charts.Bar(document.getElementById('chart_div1')); chart.draw(data1, options1); }
在此情况下, 下行中 最后的逗号 存在或缺失
['SBRF-9.16', 546.00, 189.00],
不影响 HTML 网页代码的性能, 这大大地简化了创建数据块的算法。请注意: 由于要绘制 'bar' 类型图表, 指定此类型为:
var chart = new google.charts.Bar(document.getElementById('chart_div1'));
容器本身放在 <body> 里:
</script> </head> <body> <!--Div 将容纳饼状图表--> <div id="chart_div1"></div> <br/> </body> </html>
2.2. 饼状图 ('corechart' 类型)
本文将使用此图表:
以下是 HTML 页面代码, 生成饼状图表的创建。将它保存为 *.html 扩展名的文件 (或下载本文末尾的 corechart.html 文件), 并在浏览器里打开此文件。
<html> <head> <!--加载 AJAX API--> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> google.charts.load('current', {'packages':['corechart']}); google.charts.setOnLoadCallback(drawChart2); function drawChart2() { var data2 = google.visualization.arrayToDataTable([ ['品种', '盈利'], ['Si-6.16', 82.00], ['Si-9.16', 56.00], ['SBRF-9.16', 546.00], ]); var options2 = { title: '品种的盈利, %', pieHole: 0.4, width: 440, height: 400, }; var chart = new google.visualization.PieChart(document.getElementById('chart_div2')); chart.draw(data2, options2); } </script> </head> <body> <!--Div 将容纳饼状图表--> <div id="chart_div2"></div> <br/> </body> </html>
功能上, 此块如同上例的方式安排, 仅在数据表示上略有变化。对于饼状图, 数据应指定如下:
var data2 = google.visualization.arrayToDataTable([ ['品种', '盈利'], ['Si-6.16', 82.00], ['Si-9.16', 56.00], ['SBRF-9.16', 546.00], ]); var options2 = { title: '品种的盈利, %', pieHole: 0.4, width: 440, height: 400, };
由于绘制的图表是 'corechart' 类型, 指向容器的版块如下所示:
var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
chart.draw(data2, options2);
2.3. 直方图 ('bar' 类型) + 饼状图 ('corechart' 类型) + 直方图 ('bar' 类型)
此例程的代码保存在 bar_corechart_bar.html 文件, 且在本文末尾可供下载。例程本身如下所示 (图像被缩小):
在一个配置里同时存在不同图表类型是更复杂的情况, 所以必须在页面上正确地安排 drawChart***() 函数, 以及用于显示图表的指向容器版块。当在单一页面里放置若干类型的图表时 (例如, 直方图和饼状图), 它们可通过 以下方式 包含:
google.charts.load('current', {'packages':['bar', 'corechart']});
bar_corechart_bar.html 文件的总体方案将是:
<html> <head> <!--加载 AJAX API--> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> google.charts.load('current', {'packages':['bar', 'corechart']}); google.charts.setOnLoadCallback(drawChart1); google.charts.setOnLoadCallback(drawChart2); google.charts.setOnLoadCallback(drawChart4); function drawChart1() { var data1 = ... var options1 = ... var chart = new google.charts.Bar(document.getElementById('chart_div1')); chart.draw(data1, options1); } function drawChart2() { var data2 = ... var options2 = ... var chart = new google.visualization.PieChart(document.getElementById('chart_div2')); chart.draw(data2, options2); } function drawChart4() { var data4 = ... var options4 = ... var chart = new google.charts.Bar(document.getElementById('chart_div4')); chart.draw(data4, options4); } </script> </head> <body> <!--Div 将容纳饼状图表--> <table> <tr> <td><div id="chart_div1"></div></td> <td><div id="chart_div2"></div></td> <td><div id="chart_div4"></div></td> </table> <br/> </body> </html>
函数 drawChart1 和 drawChart4 绘制直方图, 而 drawChart2 函数 绘制饼状图表。
3. 从终端 (当前交易账户) 运行分析图表
最简单的选项是使用一个小脚本。当挂载到图表上, 它开始根据入场时间浏览并显示交易的分析图表。重要的事情是指定开始绘图分析图的日期 (输入参数"start"):
//+------------------------------------------------------------------+ //| test_report.mq5 | //| 版权所有 2016, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2016, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- input datetime start=D'2010.05.11 12:05:00'; #include <DistributionOfProfits.mqh> //--- CDistributionOfProfits Analysis; //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- Analysis.AnalysisTradingHistory(start); Analysis.ShowDistributionOfProfits(); } //+------------------------------------------------------------------+
由于 "DistributionOfProfits.mqh" 文件调用系统 DLL, 当运行脚本时必须允许 DLL 导入:
4. 从策略测试器运行分析图表
例如, 我们来使用从标准分发集中的 "MACD Sample.mq5" 智能程序 (数据目录\MQL5\Experts\Examples\MACD\MACD Sample.mq5)。将智能程序复制到一个单独的文件夹, 以便所做的更改不会影响原始文件。之后更名为 "MACD Sample report.mq5"。以下是为 EA 所做的改变:
//+------------------------------------------------------------------+ //| MACD Sample report.mq5 | //| 版权所有 2009-2016, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2009-2016, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "5.50" #property description "重要的是确认智能程序可在一般的" #property description "图表上工作, 且在我们的情况里, 用户不能在输入变量" #property description "设置理有任何错误 (手数, 止盈, 尾随停止)," #property description "我们会在图表上超过 2*trend_period 根柱线上检查止盈" #define MACD_MAGIC 1234502 //--- #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\PositionInfo.mqh> #include <Trade\AccountInfo.mqh> #include <DistributionOfProfits.mqh> //--- input double InpLots =0.1; // 手数 input int InpTakeProfit =50; // 止盈 (点数) input int InpTrailingStop =30; // 尾随停止级别 (点数) input int InpMACDOpenLevel =3; // MACD 开盘级别 (点数) input int InpMACDCloseLevel=2; // MACD 平仓级别 (点数) input int InpMATrendPeriod =26; // MA 趋势周期 //--- int ExtTimeOut=10; // 在交易操作之间的超时秒数 CDistributionOfProfits ExtDistribution; //+------------------------------------------------------------------+ //| MACD 示例智能程序类 | //+------------------------------------------------------------------+ class CSampleExpert { protected:
在十分靠后的地方添加 OnTester() 函数:
//+------------------------------------------------------------------+ //| 测试器函数 | //+------------------------------------------------------------------+ double OnTester() { //--- double ret=0.0; ExtDistribution.AnalysisTradingHistory(0); ExtDistribution.ShowDistributionOfProfits(); //--- return(ret); }
在开始测试之前, 必须在终端设置中允许使用 DLL:
页面上的是分析图表结果, 此处看到的的盈利/亏损分布图表是按小时统计的:
很明显, 哪些时段开仓可获利, 以及哪些时段无利可图。在图表上标记出两个时间间隔亏损。如果我们在指定的不宜时间段内限制交易会如何?甚或走得更远 - 将交易信号反转为相对方向, 以便获得利润, 而非亏损?欢迎读者自行测试。
在策略测试器里运行多货币 EA 也许可以得到有趣的结果。作为一个例子, 使用来自代码库的免费 多货币智能程序。此外, 在 EA 的头部指定了 "#include <DistributionOfProfits.mqh>" 文件, 并声明 "CDistributionOfProfits ExtDistribution" 变量, 并且已在代码结尾添加了 "OnTester()" 函数。一次运行之后, 得到以下统计: "TestAnalysis.htm"。
注意, 所有的图表是交互的。例如, 在第一个直方图 — "品种的盈利/亏损 (合计)" — 每个品种的数值可以通过悬停在任意列来查看:
现在是饼状图: 它们显示交易期间每个品种贡献的盈利和亏损:
图表的其余部分显示了每个品种根据仓位的入场时间的盈利:
波澜不惊的 "平静" 优化
绘制交易分布的任务已经完成。在此, 交易历史并合为仓位, 并为专门的仓位绘制分析图表。仓位的盈利或亏损按照入场时间以三种方式分析: 按小时, 按周内天数以及按月份。
随本文提供的源代码的帮助下, 可以检查任何策略的有利和不利的交易小时或交易日。谁知道呢, 也许, 除一个初创策略外您可能会得到一两个新的替代品, 例如, 一种趋势和逆势策略。或者, 哪些交易时段(亚洲, 欧洲或美国) 应避免, 而不去交易, 可能会变得更清晰。随意尝试这种 "平静" 的优化方法, 它不需要任何在策略测试器中的额外运行。
变化
简要说一下文章首次发表后, 有关代码已完成的变化。
"DistributionOfProfits.mqh" v.1.027: 引入优化期间程序操作的保护。构造器包括名为 "Non File" 的文件, 和两个公有函数, 执行 MQL_OPTIMIZATION 常量的检查:
//+------------------------------------------------------------------+ //| 构造器 | //+------------------------------------------------------------------+ CDistributionOfProfits::CDistributionOfProfits(void) : m_name_file("Non File"), m_color_loss("ed7d31"), m_color_profit("5b9bd5"), m_width("440"), m_height("400"), m_data_number(1) { } //+------------------------------------------------------------------+ //| 分析交易历史 | //+------------------------------------------------------------------+ bool CDistributionOfProfits::AnalysisTradingHistory(const datetime start_time=0) { //--- if(MQLInfoInteger(MQL_OPTIMIZATION)) return(false); //+------------------------------------------------------------------+ //| 显示盈利分布 (浏览器开始) | //+------------------------------------------------------------------+ void CDistributionOfProfits::ShowDistributionOfProfits(void) { //--- if(MQLInfoInteger(MQL_OPTIMIZATION)) return;
"DistributionOfProfits.mqh" v.1.033: 现在可以按资金, 按点数, 按资金和点数分析仓位。
分析类型设置如下:
//+------------------------------------------------------------------+ //| test_report.mq5 | //| 版权所有 2016, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2016, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //+------------------------------------------------------------------+ //| 报告类型 | //+------------------------------------------------------------------+ enum ANALYSIS_TYPE { ONLY_MONEY=0, // 按资金 ONLY_POINTS=1, // 按点数 MONEY_AND_POINTS=2, // 按资金和点数 }; //--- input datetime start=D'2016.06.28 09:10:00'; input ANALYSIS_TYPE report_type=MONEY_AND_POINTS; //--- #include <DistributionOfProfits.mqh> //--- CDistributionOfProfits Analysis; //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- Analysis.SetTypeOfAnalysis(report_type); Analysis.AnalysisTradingHistory(start); Analysis.ShowDistributionOfProfits(); } //+------------------------------------------------------------------+