交易员之活学活用: "平静" 优化或绘制交易分布·进阶篇
(2/3)· 多数回测只调参数不察时机,本文用仓位级盈利分布揭开入场优劣时刻
按持仓归并历史成交盈亏
做仓位级统计时,MT5 的成交历史(deal)和持仓(position)不是一一对应的:一次持仓平仓可能拆成多笔 deal 入账。下面这段逻辑用结构体数组把同一持仓的盈亏累加归并,避免重复计算。 核心思路是先拿 HistorySelect 拉取指定时间区间内的成交,遍历每笔 deal 只认 DEAL_TYPE_BUY / DEAL_TYPE_SELL,取出 deal_id 与 profit。再用一个循环在 arr_struct_positions 里按 id 比对:找到了就累加 profit 并置 seach=true 后 break;没找到就 ArrayResize 扩一位,把新 id 和 profit 写进去。 归并完后用 ArraySize 取数组长度,逐条 Print 出「id 盈利」便于在专家日志里肉眼核对。外汇与贵金属杠杆高,历史盈亏仅作复盘参考,实盘滑点可能让相同策略的归并结果偏离。 别让结构体默认零值坑你 结构体 struct_positions 显式写了构造器把 id 和 profit 初始化为 0,否则 MQL5 局部数组元素可能是内存残值,导致首笔归并显示乱码或巨幅假盈利。开 MT5 跑这段时,先故意留一版不写构造器对比日志就能看出来。
arr_struct_positions[j].position_profit+=profit; seach=true; class="kw">break; } } if(!seach) { ArrayResize(arr_struct_positions,number+class="num">1); arr_struct_positions[number].position_id=deal_id; arr_struct_positions[number].position_profit+=profit; } } } class=class="str">"cmt">//--- class="type">int number=ArraySize(arr_struct_positions); for(class="type">int i=class="num">0;i<number;i++) { Print("id ",arr_struct_positions[i].position_id," 盈利 ",arr_struct_positions[i].position_profit); } } class=class="str">"cmt">//--- 仓位盈利结构; class="kw">struct struct_positions { class="type">long id; class="type">class="kw">double profit; class=class="str">"cmt">//--- 构造器 struct_positions() {id=class="num">0; profit=class="num">0.0;} }; struct_positions arr_struct_positions[]; class=class="str">"cmt">//--- 请求交易历史 HistorySelect(start,TimeCurrent()); class="type">uint total =HistoryDealsTotal(); class="type">ulong ticket =class="num">0; class="type">long deal_id =class="num">0; class="type">class="kw">double profit =class="num">0; class="type">long type =class="num">0; class=class="str">"cmt">//--- 对于所有成交 for(class="type">uint i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- 尝试得到成交单号 if((ticket=HistoryDealGetTicket(i))>class="num">0) { ... class=class="str">"cmt">//--- 只有买入或卖出 if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL) { ... } } } class="type">bool seach=false; class="type">int number=ArraySize(arr_struct_positions); for(class="type">int j=class="num">0;j<number;j++) { if(arr_struct_positions[j].id==deal_id) { arr_struct_positions[j].profit+=profit; seach=true; class="kw">break;
「按持仓标识归并成交记录」
这段逻辑处理的是:当一笔成交(deal)找不到已有持仓标识(seach 为 false)时,就把该标识对应的结构体塞进数组,并累加 profit。具体看,ArrayResize 把数组扩 1,用 deal_id 写入 id 字段,profit 字段做累加——这意味着同一 POSITION_IDENTIFIER 下的多笔 deal 会被折叠成一条汇总。 下方日志是 2016.09.06 15:05 在 USDJPY_M1 图表跑 position_reversal_v3 时的真实输出:EURUSD 先 Buy 0.01(POSITION_IDENTIFIER #96803513),6 秒后 Sell 0.02,后者拆成 volume 0.01(profit 0.00)与 volume 0.02(profit -0.08)两笔 deal,都挂在同一 #96803513 下。外汇与贵金属杠杆交易风险高,这种归并若漏掉反向平仓的 deal,净值统计会偏误。 最后一段 for 循环把数组打印出来:ArraySize 取长度,逐条 Print 'id + 盈利'。你开 MT5 把这段接到历史成交遍历里,能直接看到每个持仓标识的总浮动盈亏,验证归并是否对齐日志中的 #96803513。
if(!seach) { ArrayResize(arr_struct_positions,number+class="num">1); arr_struct_positions[number].id=deal_id; arr_struct_positions[number].profit+=profit; } class=class="str">"cmt">//--- class="type">int number=ArraySize(arr_struct_positions); for(class="type">int i=class="num">0;i<number;i++) { Print("id ",arr_struct_positions[i].id," 盈利 ",arr_struct_positions[i].profit); }
◍ 从日志看反手策略的成交与盈亏切片
一段真实的 EA 日志能暴露反手逻辑的执行细节。上面这组记录发生在 2016.09.06 15:05:41 到 15:05:48 的 7 秒内,EA 名为 position_reversal_v3,挂在 USDJPY 的 M1 周期上,却对 EURUSD 下了单——跨品种反手在 MT5 里完全可行,但外汇与贵金属本身杠杆高、滑点跳变频繁,这类操作风险偏大。 具体看成交:15:05:41.767 开了一笔 EURUSD 初始仓 0.02 手,持仓 ID 96803543;约 6 秒后触发 PositionClose,随后三笔 Deal 回写盈亏——0.01 手那笔利润 0.00,0.02 手亏 0.08,另一笔 0.01 手亏 0.05。总亏损 0.13 美元虽小,但 7 秒内连平带开说明反手频率偏高,点差和佣金会悄悄吃掉边际收益。 为了把这类日志结构化,可以用下面的 C 风格结构体把每笔持仓的 ID、时间、亏损、利润和品种名存下来,方便后续统计胜率和最大回撤。 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;} }; 逐行拆解:long id 存持仓唯一编号,对应日志里的 ORDER_POSITION_ID;datetime time 记录开仓或平仓时间,精度到毫秒;double loss 与 double profit 分别归集该笔的负收益和正收益;string symbol_name 标记品种如 EURUSD;构造器把字段清零,避免未初始化导致的历史回测脏数据。打开 MT5 的「专家日志」面板,对照这个结构体字段,你就能自己写个脚本把成交流导成 CSV 验证。
class="kw">struct struct_positions { class="type">long id; class="type">class="kw">datetime time; class="type">class="kw">double loss; class="type">class="kw">double profit; class="type">class="kw">string symbol_name; class=class="str">"cmt">//--- 构造器 struct_positions() {id=class="num">0; time=class="num">0; loss=class="num">0.0; profit=class="num">0.0; symbol_name=NULL;} };
把品种盈亏丢进浏览器图表
想在不依赖 MT5 报表界面的情况下看品种表现,可以用谷歌图表服务把分析画到 HTML 页面里,再用系统默认浏览器打开。MT5 侧通过 WinAPI 的 ShellExecute 拉起页面,分析逻辑只管把数据吐成网页。 直方图用 'bar' 类型,饼状图走 'corechart' 类型。两者都要求在 <head> 里先引 loader.js,再用 setOnLoadCallback 绑定绘图函数,数据块、选项块、容器指向三者缺一不可。 写数据行时有个省事细节:arrayToDataTable 的每行末尾逗号写不写都不影响页面性能,算法拼数据时可不必严判末行。下面这段是 'bar' 类型最小可运行样例,已经带了三个品种的真实盈亏数字——Si-6.16 盈利 82.00 亏损 944.00,Si-9.16 盈利 56.00 亏损 11.00,SBRF-9.16 盈利 546.00 亏损 189.00。 把代码存成 *.html 双击打开,就能看到垂直柱状图。若要同页混排直方图和饼图,只需把各 drawChart***() 函数并列,并在 <body> 里放对应 id 的 div 容器,浏览器会按回调顺序渲染。
<html>
<head>
<!--加载 AJAX API-->
<script type="text/javascript" src="https:class=class="str">"cmt">//www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load(&class="macro">#x27;current&class="macro">#x27;, {&class="macro">#x27;packages&class="macro">#x27;:[&class="macro">#x27;bar&class="macro">#x27;]});
google.charts.setOnLoadCallback(drawChart1);
function drawChart1() {
var data1 = google.visualization.arrayToDataTable([
[&class="macro">#x27;品种&class="macro">#x27;, &class="macro">#x27;盈利&class="macro">#x27;, &class="macro">#x27;亏损&class="macro">#x27;],
[&class="macro">#x27;Si-class="num">6.16&class="macro">#x27;, class="num">82.00, class="num">944.00],
[&class="macro">#x27;Si-class="num">9.16&class="macro">#x27;, class="num">56.00, class="num">11.00],
[&class="macro">#x27;SBRF-class="num">9.16&class="macro">#x27;, class="num">546.00, class="num">189.00],
]);
var options1 = {
chart: {
title: &class="macro">#x27;品种的盈利/亏损&class="macro">#x27;,
subtitle: &class="macro">#x27;合计&class="macro">#x27;,
},
bars: &class="macro">#x27;vertical&class="macro">#x27;,
vAxis: {format: &class="macro">#x27;decimal&class="macro">#x27;},
width: class="num">440,
height: class="num">400,
colors: [&class="macro">#x27;class="macro">#5b9bd5&class="macro">#x27;, &class="macro">#x27;class="macro">#ed7d31&class="macro">#x27;, &class="macro">#x27;class="macro">#7570b3&class="macro">#x27;]
};
var chart = new google.charts.Bar(document.getElementById(&class="macro">#x27;chart_div1&class="macro">#x27;));
chart.draw(data1, options1);
}
</script>
</head>
<body>
<!--Div 将容纳饼状图表-->
<div id="chart_div1"></div>
<br/>
</body>
</html>「用谷歌图表把品种盈亏拆开看」
把交易报告的盈亏做可视化,最省事的办法是直接套 Google Charts 的 Bar 和 Pie 两套组件。下面这段脚本里,data1 用数组塞了三个品种:'Si-6.16' 盈利 82.00、'Si-9.16' 盈利 56.00、'SBRF-9.16' 盈利 546.00 且亏损 189.00,柱子垂直排布、宽 440 高 400,蓝橙紫三色区分序列。 第二个 html 块另起一个饼图,只取盈利百分比:Si-6.16 占 82.00、Si-9.16 占 56.00、SBRF-9.16 占 546.00,pieHole 设 0.4 做成环形。你在 MT5 里导出的成交明细,只要换成这种 arrayToDataTable 的二维数组,丢到本地 html 用浏览器打开就能直接出图。 外汇与贵金属品种波动大、杠杆高,这类盈亏图仅反映历史样本,后续分布可能明显偏移,别拿单段回测当仓位依据。
var data1 = google.visualization.arrayToDataTable([ [&class="macro">#x27;品种&class="macro">#x27;, &class="macro">#x27;盈利&class="macro">#x27;, &class="macro">#x27;亏损&class="macro">#x27;], [&class="macro">#x27;Si-class="num">6.16&class="macro">#x27;, class="num">82.00, class="num">0.00], [&class="macro">#x27;Si-class="num">9.16&class="macro">#x27;, class="num">56.00, class="num">0.00], [&class="macro">#x27;SBRF-class="num">9.16&class="macro">#x27;, class="num">546.00, class="num">189.00], ]); var options1 = { chart: { title: &class="macro">#x27;品种的盈利/亏损&class="macro">#x27;, subtitle: &class="macro">#x27;合计&class="macro">#x27;, }, bars: &class="macro">#x27;vertical&class="macro">#x27;, vAxis: {format: &class="macro">#x27;decimal&class="macro">#x27;}, width: class="num">440, height: class="num">400, colors: [&class="macro">#x27;class="macro">#5b9bd5&class="macro">#x27;, &class="macro">#x27;class="macro">#ed7d31&class="macro">#x27;, &class="macro">#x27;class="macro">#7570b3&class="macro">#x27;] }; var chart = new google.charts.Bar(document.getElementById(&class="macro">#x27;chart_div1&class="macro">#x27;)); chart.draw(data1, options1); var data2 = google.visualization.arrayToDataTable([ [&class="macro">#x27;品种&class="macro">#x27;, &class="macro">#x27;盈利&class="macro">#x27;], [&class="macro">#x27;Si-class="num">6.16&class="macro">#x27;, class="num">82.00], [&class="macro">#x27;Si-class="num">9.16&class="macro">#x27;, class="num">56.00], [&class="macro">#x27;SBRF-class="num">9.16&class="macro">#x27;, class="num">546.00], ]); var options2 = { title: &class="macro">#x27;品种的盈利, %&class="macro">#x27;, pieHole: class="num">0.4, width: class="num">440, height: class="num">400, }; var chart = new google.visualization.PieChart(document.getElementById(&class="macro">#x27;chart_div2&class="macro">#x27;)); chart.draw(data2, options2);
◍ 把回测盈利直接喂给网页图表
在 MT5 里跑完多品种回测后,最费时间的不是算收益,而是把结果整理成能一眼看懂的图。上面这段脚本把三个合约的盈利数据直接塞进 Google Charts:Si-6.16 盈利 82.00、Si-9.16 盈利 56.00、SBRF-9.16 盈利 546.00,单位均为百分比。 drawChart2 里用 visualization.PieChart 画甜甜圈图(pieHole: 0.4),宽度 440、高度 400,DOM 挂在 chart_div2 上;drawChart1 和 drawChart4 则走 charts.Bar 柱状图,分别绑 chart_div1、chart_div4。三个回调都在 google.charts.setOnLoadCallback 里注册,页面 load 完后自动出图。 你只需把 data2 的数组换成自己 EA 的回测输出,本地起个 html 双击就能看分布。外汇和贵金属品种波动大、杠杆高,回测盈利不代表实盘概率,图表只是帮你看清样本偏差。
[&class="macro">#x27;品种&class="macro">#x27;, &class="macro">#x27;盈利&class="macro">#x27;], [&class="macro">#x27;Si-class="num">6.16&class="macro">#x27;, class="num">82.00], [&class="macro">#x27;Si-class="num">9.16&class="macro">#x27;, class="num">56.00], [&class="macro">#x27;SBRF-class="num">9.16&class="macro">#x27;, class="num">546.00], ]); var options2 = { title: &class="macro">#x27;品种的盈利, %&class="macro">#x27;, pieHole: class="num">0.4, width: class="num">440, height: class="num">400, }; var chart = new google.visualization.PieChart(document.getElementById(&class="macro">#x27;chart_div2&class="macro">#x27;)); chart.draw(data2, options2); google.charts.load(&class="macro">#x27;current&class="macro">#x27;, {&class="macro">#x27;packages&class="macro">#x27;:[&class="macro">#x27;bar&class="macro">#x27;, &class="macro">#x27;corechart&class="macro">#x27;]}); <html> <head> <!--加载 AJAX API--> <script type="text/javascript" src="https:class=class="str">"cmt">//www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> google.charts.load(&class="macro">#x27;current&class="macro">#x27;, {&class="macro">#x27;packages&class="macro">#x27;:[&class="macro">#x27;bar&class="macro">#x27;, &class="macro">#x27;corechart&class="macro">#x27;]}); 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(&class="macro">#x27;chart_div1&class="macro">#x27;)); chart.draw(data1, options1); } function drawChart2() { var data2 = ... var options2 = ... var chart = new google.visualization.PieChart(document.getElementById(&class="macro">#x27;chart_div2&class="macro">#x27;)); chart.draw(data2, options2); } function drawChart4() { var data4 = ... var options4 = ... var chart = new google.charts.Bar(document.getElementById(&class="macro">#x27;chart_div4&class="macro">#x27;)); 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>