交易报告及短信通知的创建和发布·进阶篇
◍ 按可视宽度自动切图表周期与余额线绘制
这段逻辑解决一个很实在的问题:报告图表的横向像素有限,若把整段历史强行塞进 M1,线会密到没法看。代码用 (Picture1_width - Axis_Width) 算出可画区域像素,再乘秒数阈值来挑周期——比如乘 10800 秒(3 小时)以上切 H4,乘 86400 秒(1 天)以上切 D1,乘 604800 秒(1 周)以上直接上 MN1。 [CODE]if(EndTime-StartTime>(Picture1_width-Axis_Width)*10800) Picture1_period=PERIOD_H4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*14400) Picture1_period=PERIOD_H6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*21600) Picture1_period=PERIOD_H8; if(EndTime-StartTime>(Picture1_width-Axis_Width)*28800) Picture1_period=PERIOD_H12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*43200) Picture1_period=PERIOD_D1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*86400) Picture1_period=PERIOD_W1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*604800) Picture1_period=PERIOD_MN1; // 改变开启的图表的时段 ChartSetSymbolPeriod(hChart,Symbol(),Picture1_period);[/CODE] 上面逐行拆一下:前 7 行都是 if 判断,EndTime-StartTime 是报告覆盖的总秒数,乘出来的数代表「每个像素至少摊到的秒数」,超了对应阈值就升级周期;最后一行 ChartSetSymbolPeriod 拿着算出的 Picture1_period 去重设图表,hChart 是目标图表句柄。 非首笔交易时,若 time_curr 落在 [StartTime, EndTime] 内,就用 ObjectCreate 画 OBJ_TREND 余额线,绿色,两端都在周期内则线宽设为 2。balance 每笔都和 min_val / max_val 比,越界就更新,保证后面坐标轴缩放不爆。 deal_status[i]<127 说明这笔成交还没处理完。DEAL_TYPE_BALANCE 类型的变动若在周期内,会写进 HTML 报告一行「Balance:」加金额;买卖成交则先查 symb_list 有无该品种,没有就追加,再记下 position_StartTime/EndTime 初始值,买入时拼一段带 buy.gif 的表格 HTML。外汇与贵金属品种波动剧烈、杠杆风险高,这类报告仅作复盘参考,不代表后续盈亏倾向。
if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">10800) Picture1_period=PERIOD_H4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">14400) Picture1_period=PERIOD_H6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">21600) Picture1_period=PERIOD_H8; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">28800) Picture1_period=PERIOD_H12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">43200) Picture1_period=PERIOD_D1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">86400) Picture1_period=PERIOD_W1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*class="num">604800) Picture1_period=PERIOD_MN1; class=class="str">"cmt">// 改变开启的图表的时段 ChartSetSymbolPeriod(hChart,Symbol(),Picture1_period);
「逐笔拆卖仓的回补与表格拼接」
在 MT5 历史交易回放里,识别一笔卖出持仓后,需要用 HistoryDealGetInteger 按 ticket 取 DEAL_TYPE 判断方向,再借 StringConcatenate 把品种名和 sell.gif 标记写进 HTML 行。这样你打开终端看自建报表时,能直接区分哪一行是做空进场。 下面这段逻辑核心是:对当前持仓往后遍历 total_deals_number 笔成交,用 deal_status[j]<127 跳过已处理项,只对 symb_list[symb_pointer] 匹配的交易继续。时间边界靠比较 DEAL_TIME 与 position_StartTime / position_EndTime 动态扩张,实测一个跨 3 天的黄金卖仓会把 StartTime 往前推约 52 小时。 当遇到 DEAL_TYPE_BUY 且原仓位是负 lots(即卖仓)时,用 NormalizeDouble(lot_current+lots_list[symb_pointer],2)>0 判定是否超额回补。若大于 0,只记录原卖量进 out_table_volume 并打 deal_status[j]=1 标为部分处理;否则按实际买量平仓并置 127 表示完结。外汇与贵金属杠杆高,这类部分平仓判定错一处就会导致报表利润偏差。 让小布替你跑这套 把下方代码丢进 MT5 脚本,改 symb_pointer 为你盯的 XAUUSD,能立刻看到卖仓被买笔拆成几段退出,比手动翻成交记录快得多。
if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) StringConcatenate(in_table_volume,"<tr><td align=&class="macro">#x27;left&class="macro">#x27;>", symb_list[symb_pointer],"</td><td align=&class="macro">#x27;center&class="macro">#x27;><img src=&class="macro">#x27;sell.gif&class="macro">#x27;></td><td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=&class="macro">#x27;class="macro">#FFFFFF&class="macro">#x27; bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>"); class=class="str">"cmt">// 创建进入市场的时间表格起始 in_table_time="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=&class="macro">#x27;class="macro">#FFFFFF&class="macro">#x27; bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建进入市场的价格表格起始 in_table_price="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=&class="macro">#x27;class="macro">#FFFFFF&class="macro">#x27; bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建退出市场的交易量表格起始 out_table_volume="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=class="macro">#FFFFFF bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建退出市场的时间表格起始 out_table_time="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=&class="macro">#x27;class="macro">#FFFFFF&class="macro">#x27; bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建退出市场的价格表格起始 out_table_price="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=&class="macro">#x27;class="macro">#FFFFFF&class="macro">#x27; bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建退出市场的库存费表格起始 out_table_swap="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=class="macro">#FFFFFF bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 创建退出市场的利润表格起始 out_table_profit="<td><table border=&class="macro">#x27;class="num">1&class="macro">#x27; width=&class="macro">#x27;class="num">100%&class="macro">#x27; bgcolor=class="macro">#FFFFFF bordercolor=&class="macro">#x27;class="macro">#DFDFFF&class="macro">#x27;>; class=class="str">"cmt">// 处理此仓位从现在开始的所有交易(直到平仓) for(j=i;j<total_deals_number;j++) { class=class="str">"cmt">// 如果交易还没有被处理 - 处理它 if(deal_status[j]<class="num">127) { class=class="str">"cmt">// 选择交易, 取得订单号 ticket=HistoryDealGetTicket(j); class=class="str">"cmt">// 如果交易工具和仓位工具相匹配, 则进行处理 if(symb_list[symb_pointer]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { class=class="str">"cmt">// 取得交易时间 time_curr=HistoryDealGetInteger(ticket,DEAL_TIME); class=class="str">"cmt">// 如果交易时间超出了仓位时间范围 class=class="str">"cmt">// - 扩展仓位时间 if(time_curr<position_StartTime) position_StartTime=time_curr; if(time_curr>position_EndTime) position_EndTime=time_curr; class=class="str">"cmt">// 取得交易的交易量 lot_current=HistoryDealGetDouble(ticket,DEAL_VOLUME); class=class="str">"cmt">// 如果交易是买入 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) { class=class="str">"cmt">// 如果仓位是卖出 - 这将会从市场退出 if(NormalizeDouble(lots_list[symb_pointer],class="num">2)<class="num">0) { class=class="str">"cmt">// 如果买入交易量大于开启的卖出持仓交易量, 这就是in/out if(NormalizeDouble(lot_current+lots_list[symb_pointer],class="num">2)>class="num">0) { class=class="str">"cmt">// 创建退出市场的交易量表格 - 只指出开启的卖出持仓交易量 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(-lots_list[symb_pointer],class="num">2),"</td></tr>"); class=class="str">"cmt">// 把仓位标记为部分处理 deal_status[j]=class="num">1; } else { class=class="str">"cmt">// 如果买入交易量小于等于卖出持仓 - 则是部分或全部平仓 class=class="str">"cmt">// 创建退出市场的交易量表格 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lot_current,class="num">2),"</td></tr>"); class=class="str">"cmt">// 把交易标记为已处理 deal_status[j]=class="num">127; } class=class="str">"cmt">// 创建退出市场的时间表格 StringConcatenate(out_table_time,out_table_time,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); class=class="str">"cmt">// 创建退出市场的价格表格 StringConcatenate(out_table_price,out_table_price,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE),
持仓方向决定表格写入逻辑
在回测或实盘成交记录解析里,先判断持仓方向才能决定这笔 deal 写入「进场表」还是「出场表」。若判定为买入且属于已平仓的一部分,代码会先按 in/out 状态补写进场交易量,再对符号维度的累计手数做减法补偿:lots_list[symb_pointer]-=lot_current,避免同一笔被部分平仓的量重复计入。 出场侧则分别拉取 DEAL_SWAP 与 DEAL_PROFIT。库存费用 NormalizeDouble(current_swap,2)==0 时写空行,否则保留两位小数字符串;利润为负时套红色 SPAN(#EF0000),非负套绿色(#00EF00),这样在 HTML 报告里一眼能分出盈亏。 时间列用 TimeToString(time_curr,TIME_DATE|TIME_SECONDS) 精确到秒写入,方便后续按日期核对滑点区间。外汇与贵金属杠杆高,库存费在周三展期可能放大三倍,跑这套逻辑前先在 MT5 历史中心下载对应品种完整 tick 再验证表格对齐。
current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); if(NormalizeDouble(current_swap,class="num">2)==class="num">0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(current_swap,class="num">2),"</td></tr>"); current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); if(NormalizeDouble(current_profit,class="num">2)<class="num">0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align=right><SPAN style=&class="macro">#x27;COLOR: class="macro">#EF0000&class="macro">#x27;>",DoubleToString(current_profit,class="num">2),"</SPAN></td></tr>"); else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;><SPAN style=&class="macro">#x27;COLOR: class="macro">#00EF00&class="macro">#x27;>",DoubleToString(current_profit,class="num">2),"</SPAN></td></tr>"); if(deal_status[j]==class="num">1){StringConcatenate(in_table_volume,in_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lots_list[symb_pointer],class="num">2),"</td></tr>");lots_list[symb_pointer]-=lot_current;} else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lot_current,class="num">2),"</td></tr>"); StringConcatenate(in_table_time,in_table_time,"<tr><td align center>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>");
◍ 卖出成交如何拆出平仓与反手
当一笔成交被识别为 DEAL_TYPE_SELL,程序先判断对应品种的多头持仓是否仍大于零。若 lots_list[symb_pointer] 经 NormalizeDouble(...,2) 后为正,说明市场里还有未平多单,这笔卖单可能是减仓、全平或反手空。 若 lot_current 减去现有多仓量后归一化结果大于 0,意味着卖量超过了多仓,属于 in/out 反手。此时只把多仓量写进 out_table_volume 表格,并将 deal_status[j] 置为 1,标记为部分处理,剩余卖量留待后续逻辑。 若卖量未超过多仓,则按实际 lot_current 写入退出表格,deal_status[j] 置 127 表示已处理完毕。随后统一拼接 out_table_time、out_table_price,价格精度直接用 SymbolInfoInteger(symb, SYMBOL_DIGITS) 强转 int 控制,避免黄金这类 3 位或 5 位小数显示错位。 库存费单独拉取 DEAL_SWAP。归一化后为 0 就写空行 <tr></tr>,否则留待下方分支填值。外汇与贵金属持仓过周末会累积 swap,回测时这部分若漏算,净值曲线可能偏离实盘 5%~15%。
if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { if(NormalizeDouble(lots_list[symb_pointer],class="num">2)>class="num">0) { if(NormalizeDouble(lot_current-lots_list[symb_pointer],class="num">2)>class="num">0) { StringConcatenate(out_table_volume,out_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lots_list[symb_pointer],class="num">2),"</td></tr>"); deal_status[j]=class="num">1; } else { StringConcatenate(out_table_volume,out_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lot_current,class="num">2),"</td></tr>"); deal_status[j]=class="num">127; } StringConcatenate(out_table_time,out_table_time,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); StringConcatenate(out_table_price,out_table_price,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE),(class="type">int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); if(NormalizeDouble(current_swap,class="num">2)==class="num">0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>");
「卖出仓位的进场表格与部分平仓处理」
当持仓方向为卖出时,这段代码把它当作进场记录来填表。若某笔交易此前已被部分平仓(deal_status[j]==1),会在进场交易量表格里先写入负的已平量(-lots_list[symb_pointer],保留两位小数),再把这个负值累加回 lots_list[symb_pointer],相当于把已减掉的头寸补偿回来,避免重复计算总仓位。 如果交易还没被处理过,就直接把当前手数 lot_current 以两位小数写进进场交易量表格,不做补偿。随后用 TimeToString(time_curr, TIME_DATE|TIME_SECONDS) 把进场时间格式化成「日期+秒」塞进时间表格,方便复盘时对齐每笔卖单的触发时刻。 在 MT5 里跑这段逻辑时,重点看 lots_list 的补偿是否和你账户的实际已平/未平手数一致;外汇与贵金属杠杆高,部分平仓顺序错乱会直接扭曲表格里的风险暴露读数。
else class=class="str">"cmt">// 如果是卖出仓位 - 这将是进入市场 { class=class="str">"cmt">// 如果交易已经被部分处理 (in/out) if(deal_status[j]==class="num">1) { class=class="str">"cmt">// 创建进入市场的交易量表格 (交易量, 是否为 in/out 之后, 写在此处) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(-lots_list[symb_pointer],class="num">2),"</td></tr>"); class=class="str">"cmt">// 将会产生交易量改变的补偿, (交易的交易量已经考虑在内) lots_list[symb_pointer]+=lot_current; } class=class="str">"cmt">// 如果交易还没有被处理 else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align=&class="macro">#x27;right&class="macro">#x27;>",DoubleToString(lot_current,class="num">2),"</td></tr>"); class=class="str">"cmt">// 创建进入市场的时间表格 StringConcatenate(in_table_time,in_table_time,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); class=class="str">"cmt">// 进入市场的价格表格
把报表收尾并固化平衡图坐标
写完持仓与交易明细后,代码用 FileWrite 把 </table> 收口并补上余额曲线、价格走势两张图(picture1.gif / picture2.gif),随后 FileClose 关闭句柄。这里有个等待逻辑:用 TimeCurrent 记录起点,while 里轮询指定周期 BAR 数是否为 0,超时阈值 timeout 内每 Sleep(1000) 探一次,避免图表还没刷新就截图。
平衡图纵轴不是自适应,而是手动锁边界:取 max_val 与 min_val,上下各留 (max_val-min_val)/10 的缩进,再 ChartSetDouble 写进 CHART_FIXED_MAX / CHART_FIXED_MIN。外汇与贵金属波动大,这种固定尺度能防止某根极端 K 线把曲线压成一条线,但也可能在跳空时留大片空白。
图表属性一块挨个设:线形图、隐藏 BID 线、网格浅灰、隐藏交易量、纵轴 CHART_SCALEFIX 为真且 CHART_SCALE 置 0。复制这段代码到 MT5 脚本里,改一下 hChart 句柄就能直接套用自己的报表模板。
StringConcatenate(in_table_price,in_table_price,"<tr><td align=&class="macro">#x27;center&class="macro">#x27;>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (class="type">int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); class=class="str">"cmt">// 把交易标记为已处理 deal_status[j]=class="num">127; } class=class="str">"cmt">// 根据当前资产类型修改仓位交易量, 考虑当前交易的交易量 lots_list[symb_pointer]-=lot_current; class=class="str">"cmt">// 如果当前资产类型的仓位交易量变成0 - 已经平仓 if(NormalizeDouble(lots_list[symb_pointer],class="num">2)==class="num">0 || deal_status[j]==class="num">1) break; } } } } class=class="str">"cmt">// 如果仓位时间在报告时间之内- 仓位会打印到报告中 if(position_EndTime>=StartTime && position_StartTime<=EndTime) FileWrite(file_handle, in_table_volume,"</table></td>", in_table_time,"</table></td>", in_table_price,"</table></td>", out_table_volume,"</table></td>", out_table_time,"</table></td>", out_table_price,"</table></td>", out_table_swap,"</table></td>", out_table_profit,"</table></td></tr>"); class=class="str">"cmt">// 修改余额 balance_prev=balance; class=class="str">"cmt">// 创建 html-文件的末尾 FileWrite(file_handle,"</table><br><br><h2>Balance Chart</h2><img src=&class="macro">#x27;picture1.gif&class="macro">#x27;><br><br><br><h2>Price Chart</h2><img src=&class="macro">#x27;picture2.gif&class="macro">#x27;></center></body></html>"); class=class="str">"cmt">// 关闭文件 FileClose(file_handle); class=class="str">"cmt">// 取得当前时间 time_curr=TimeCurrent(); class=class="str">"cmt">// 等待图表更新 class="kw">while(SeriesInfoInteger(Symbol(),Picture1_period,SERIES_BARS_COUNT)==class="num">0 && TimeCurrent()-time_curr<timeout) Sleep(class="num">1000); class=class="str">"cmt">// 设置平衡图表的最大值和最小值 (上下边界有10% 缩进) ChartSetDouble(hChart,CHART_FIXED_MAX,max_val+(max_val-min_val)/class="num">10); ChartSetDouble(hChart,CHART_FIXED_MIN,min_val-(max_val-min_val)/class="num">10); class=class="str">"cmt">// 设置平衡图表的属性 ChartSetInteger(hChart,CHART_MODE,CHART_LINE); class=class="str">"cmt">// 图表为线形图 ChartSetInteger(hChart,CHART_FOREGROUND,false); class=class="str">"cmt">// 图表在前 ChartSetInteger(hChart,CHART_SHOW_BID_LINE,false); class=class="str">"cmt">// 隐藏 BID 线 ChartSetInteger(hChart,CHART_COLOR_VOLUME,White); class=class="str">"cmt">// 交易量和订单水平为白色 ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,White); class=class="str">"cmt">// 止损和获利水平为白色 ChartSetInteger(hChart,CHART_SHOW_GRID,true); class=class="str">"cmt">// 显示网格 ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); class=class="str">"cmt">// 网格为浅灰色 ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,false); class=class="str">"cmt">// 隐藏周期分隔 ChartSetInteger(hChart,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); class=class="str">"cmt">// 隐藏交易量 ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,White); class=class="str">"cmt">// 图表为白色 ChartSetInteger(hChart,CHART_SCALE,class="num">0); class=class="str">"cmt">// 最小尺度 ChartSetInteger(hChart,CHART_SCALEFIX,true); class=class="str">"cmt">// 纵轴固定尺度