
交易报告及短信通知的创建和发布
简介
本文会讲述如何生成一份 HTML 文件格式的交易结果报告(利用“EA 交易”、指标或脚本),并通过 FTP 将其上传到 WWW 服务器。我们还会考虑以短消息形式向手机发送交易事件通知。
想要更自如地阅读本文内容,建议读者熟悉 HTML (超文本标记语言)知识。
要实施报告上传,我们需要一个可以通过 FTP 接受数据的 WWW 服务器(可以是任何计算机)。而要实现接收短消息形式的交易事件通知,我们需要一个电子邮箱 - 短消息的网关(大多数移动运营商和第三方组织都提供该服务)。
1. 创建报告并通过 FTP 发送
我们来创建一个 MQL5 程序,并利用它来生成一份交易报告,再将报告通过 FTP 协议发送出去。首先,我们将其制作为脚本。将来我们能用它作为一个成品块,插入到“EA 交易”和指标中。比如说,在“EA 交易”中,您可以将此程序块作为 Trade 或 Timer 事件处理程序,于交易请求后运行此块,或是为 ChartEvent 事件设定一些动作。而在指标中,您则可以将此块加入到 Timer 或 ChartEvent 事件处理程序中。
程序创建的报告示例,如图 1、2、3 所示。或者,您也可以通过本文末尾处的链接下载该报告。
图 1. 报告示例 - 交易与仓位表
图 2. 报告示例 - 平衡图
图 3. 报告示例 - 当前工具的价格图表
交易与仓位表(图 1)中,为方便起见,所有交易都被划分成仓位。该表的左侧,会显示进入市场(持仓和添加)的交易量、时间和价格。表右侧显示的则是退出市场(部分或完全平仓)的上述参数。根据 in/out (买入/卖出),交易为分两个部分 - 一个平仓,下一个开仓。
交易与仓位表的下方是平衡图表(横轴 - 时间),而其底部则是当前工具价格图表。
该程序会在 MetaTarder5_istall_dir\MQL5\Files 文件夹中创建 "report.html"、"picture1.gif" 和 "picture2.gif" 文件(html 格式报告文件、平衡图表与价格图表的图像文件)。而且终端设置中的 FTP 发布已启用 - 它会向指定服务器发送上述三种文件。此外,我们还需要两种文件 - 带有指向持仓方向箭头的图像 - 买入或卖出("buy.gif" 与 "sell.gif")。您可以采用这些图像(下载链接在文末),也可以利用任何图形编辑器自行绘制。而这两个文件也要放到 WWW 服务器中与 "report.html" 文件相同的文件夹。
作为输入参数,该程序接受报告生成周期的开始与结束时间。本例中,报告周期的结束为当前时间,用户选择报告周期的变体:整个周期、昨天、上周、上月或去年。
简单说说我们创建报告的方式。所有可用的交易历史都会请求交易服务器。获取到的成交会被一个接一个地处理。deal_status[] 数组会存储交易是否被处理的相关信息。该数组的元素索引,就是从交易服务器交易列表中接收到的交易编号。而元素的值则如下阐释:0 - 交易尚未处理,1 - 交易已被部分处理 (in/out),127 - 交易处理完毕(其它值未被使用,留作后用)。
symb_list[] 数组包含交易得以执行的金融工具名称的列表,而 lots_list[] 数组则包含交易处理时每个工具的开仓交易量。交易量正值对应的是买入持仓,而负值则对应着卖出持仓。如果交易量等于零,则意味着该工具没有敞口仓位。如果交易处理期间出现了未于列表中(symb_list[] 数组中)出现的金融工具 - 则将其添加到那里,且金融工具的编号(symb_total 变量)以 1 为增量增长。
根据每个交易处理过程,每一个后续交易都利用相同的金融工具进行分析,直到平仓或买入/卖出。只有 deal_status[] 数组值小于 127 的那些交易会被分析。交易处理之后,对应的 deal_status[] 数组元素即被赋值为 127,如果交易为仓位 in/out,则赋值 1。如果开仓时间匹配报告周期(由 StartTime 和 EndTime 变量定义) - 则此仓位被记录到报告中(所有的输入和输出)。
除交易表外,还有一个新的当前金融工具图表被打开。此图表的所有必要属性均已提供,并用 ChartScreenShot() 函数做了一个屏幕截图 - 这样我们就能获取带有当前工具价格图表的图像文件了。接下来,此图表上的价格图表会被隐藏,余额变动图表会被绘制,然后再创建另一个屏幕截图。
两个带有图表的图像文件和带有报告的 HTML 文件创建之后,通过 FTP 发送文件的功能即被勾选。如果允许 - 则会根据 MetaTrader 5 中指定的设置,利用 SendFTP() 函数发送 "report.html"、"picture1.gif" 和 "picture2.gif" 文件。
启动 MetaQuotes Language Editor (语言编辑器),开始创建一个脚本。定义常量 - 图表刷新超时(以秒计)、价格图表的宽度和高度以及平衡图表的最大宽度。显示余额变动曲线的图表周期,根据报告周期的期限和图表的最大宽度进行选择。图表的宽度调整为平衡图所需的尺寸。
图表的高度自动计算为宽度的一半。我们还要将纵轴的宽度指定为常量 - 即图形面积相比图像宽度因纵轴而缩减的像素数。
#define timeout 10 // 图表刷新时间 #define Picture1_width 800 // 报告中图表最大宽度 #define Picture2_width 800 // 报告中价格图表宽度 #define Picture2_height 600 // 报告中价格图表高度 #define Axis_Width 59 // 纵轴宽度 (以像素为单位)
指定将从用户请求的输入参数。
// 请求输入参数 #property script_show_inputs
创建报告周期的枚举。
// 报告周期的枚举 enum report_periods { All_periods, Last_day, Last_week, Last_month, Last_year };
就报告周期询问用户(默认为整个周期)。
// 询问报告周期 input report_periods ReportPeriod=0;
编写 OnStart() 函数的主体。
void OnStart() {
确定报告周期的开头和结尾。
datetime StartTime=0; // 报告周期起始时间 datetime EndTime=TimeCurrent(); // 报告周期结束时间 // 计算报告周期起始时间 switch(ReportPeriod) { case 1: StartTime=EndTime-86400; // 日 break; case 2: StartTime=EndTime-604800; // 周 break; case 3: StartTime=EndTime-2592000; // 月 break; case 4: StartTime=EndTime-31536000; // 年 break; } // 如果不是以上的选项, 那么 StartTime=0 (整个周期)
声明将在本程序内使用的变量。变量的用途描述见评论。
int total_deals_number; // 历史数据中的交易总数 int file_handle; // 文件句柄 int i,j; // 循环计数器 int symb_total; // 交易中的资产数量 int symb_pointer; // 当前资产的指针 char deal_status[]; // 交易状态 (处理/未处理) ulong ticket; // 交易订单号 long hChart; // 图表 id double balance; // 当前余额值 double balance_prev; // 之前余额值 double lot_current; // 当前交易交易量 double lots_list[]; // 根据资产种类的开放交易量列表 double current_swap; // 当前交易的库存费 double current_profit; // 当前交易的利润 double max_val,min_val; // 最大值和最小值 string symb_list[]; // 交易的资产种类列表 string in_table_volume; // 建仓交易量 string in_table_time; // 进场时间 string in_table_price; // 进场价格 string out_table_volume; // 出场交易量 string out_table_time; // 出场时间 string out_table_price; // 出场价格 string out_table_swap; // 出场库存费 string out_table_profit; // 出场利润 bool symb_flag; // 资产在列表中的标志 datetime time_prev; // 前面的时间值 datetime time_curr; // 当前时间值 datetime position_StartTime; // 建仓时间 datetime position_EndTime; // 最后一个退场时间 ENUM_TIMEFRAMES Picture1_period; // 余额图表时段
打开一个新图表并设置其属性 - 这是一个价格图表,会在报告底部输出。
// 开启一个新图表并设置其属性 hChart=ChartOpen(Symbol(),0); ChartSetInteger(hChart,CHART_MODE,CHART_BARS); // 柱状图 ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // 启用自动滚动 ChartSetInteger(hChart,CHART_COLOR_BACKGROUND,White); // 背景色为白色 ChartSetInteger(hChart,CHART_COLOR_FOREGROUND,Black); // 线和标签为黑色 ChartSetInteger(hChart,CHART_SHOW_OHLC,false); // 不显示OHLC ChartSetInteger(hChart,CHART_SHOW_BID_LINE,true); // 显示 BID 线 ChartSetInteger(hChart,CHART_SHOW_ASK_LINE,false); // 隐藏 ASK 线 ChartSetInteger(hChart,CHART_SHOW_LAST_LINE,false); // 隐藏 LAST 线 ChartSetInteger(hChart,CHART_SHOW_GRID,true); // 显示网格线 ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,true); // 显示时段分隔符 ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // 网格线为浅灰色 ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,Black); // 图表线为黑色 ChartSetInteger(hChart,CHART_COLOR_CHART_UP,Black); // 向上的柱为黑色 ChartSetInteger(hChart,CHART_COLOR_CHART_DOWN,Black); // 向下的柱为黑色 ChartSetInteger(hChart,CHART_COLOR_BID,Gray); // BID 线为灰色 ChartSetInteger(hChart,CHART_COLOR_VOLUME,Green); // 交易量和订单水平为绿色 ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,Red); // SL 和 TP 水平为红色 ChartSetString(hChart,CHART_COMMENT,ChartSymbol(hChart)); // 注释包含资产种类
图表截屏,并将其保存为 "picture2.gif"。
// 把图表保存为图像文件 ChartScreenShot(hChart,"picture2.gif",Picture2_width,Picture2_height);
请求现有账户整个时间段的交易历史。
// 请求整个时段的交易历史 HistorySelect(0,TimeCurrent());
打开 "report.html" 文件,并在其中写入带有报告的 HTML 页面(ANSI 编码)。
// 打开图表文件 file_handle=FileOpen("report.html",FILE_WRITE|FILE_ANSI);
编写 HTML 文档的开头部分:
- html 文档的开头 (<html>)
- 显示于您浏览器窗口顶部的标题 (<head><title>“EA 交易报告”</title></head>)
- 带有背景色的 html 文档主体部分的开头 (<body bgcolor='#EFEFEF'>)
- 居中对齐 (<center>)
- 交易与仓位表的标题 (<h2>交易报告</h2>)
- 交易与仓位表的开头,带有对齐、边框宽度、背景颜色、边框颜色、单元格间距及单元格填充 (<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>)
- 表标题
// 写 HTML 开头部分 FileWrite(file_handle,"<html>"+ "<head>"+ "<title>Expert Trade Report</title>"+ "</head>"+ "<body bgcolor='#EFEFEF'>"+ "<center>"+ "<h2>Trade Report</h2>"+ "<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>"+ "<tr>"+ "<th rowspan=2>SYMBOL</th>"+ "<th rowspan=2>Direction</th>"+ "<th colspan=3>Open</th>"+ "<th colspan=3>Close</th>"+ "<th rowspan=2>Swap</th>"+ "<th rowspan=2>Profit</th>"+ "</tr>"+ "<tr>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "</tr>");
获取列表中的交易数量。
// 历史中的交易数 total_deals_number=HistoryDealsTotal();
为 symb_list[]、lots_list[] 和 deal_status[] 数组设定维度。
// 设置资产列表,交易量列表和交易状态数组的维度 ArrayResize(symb_list,total_deals_number); ArrayResize(lots_list,total_deals_number); ArrayResize(deal_status,total_deals_number);
初始化带有 0 值的所有 deal_status[] 数组元素 - 所有交易都未处理。
// 把数组的所有原色设为0 - 交易没有被处理 ArrayInitialize(deal_status,0);
设置余额与变量的初始值,用于存储余额的前一值。
balance=0; // 初始余额 balance_prev=0; // 之前余额
设置变量的初始值,用于存储列表中的金融工具数量。
// 列表中的资产数目 symb_total=0;
创建一个顺序处理列表中每个交易的循环。
// 处理历史中的所有交易 for(i=0;i<total_deals_number;i++) {
选择当前交易并获取其订单号。
//选择交易,取得订单号 ticket=HistoryDealGetTicket(i);
利用当前交易中的利润额更改余额。
// 修改余额 balance+=HistoryDealGetDouble(ticket,DEAL_PROFIT);
获取交易时间 - 之后会频繁用到。
// 读取交易时间 time_curr=HistoryDealGetInteger(ticket,DEAL_TIME);
如果它是列表中的第一个交易 - 我们需要根据图表标绘的报告周期和区域宽度,调整报告周期的界限并选择平衡图表的周期。设置最大与最小余额的初始值(这些变量将被用于设置图表的最大与最小值)。
// 如果是第一笔交易 if(i==0) { // 如果报告时段开始早于第一笔订单 // 则报告时段将从第一笔交易开始 if(StartTime<time_curr) StartTime=time_curr; // 如果报告时段结束比当前时间晚, // 则报告时段的结束时间等于当前时间 if(EndTime>TimeCurrent()) EndTime=TimeCurrent(); // 初始化最大最小余额 // 等于当前余额 max_val=balance; min_val=balance; // 根据报告时段计算余额图表 // 时间段 Picture1_period=PERIOD_M1; if(EndTime-StartTime>(Picture1_width-Axis_Width)) Picture1_period=PERIOD_M2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*120) Picture1_period=PERIOD_M3; if(EndTime-StartTime>(Picture1_width-Axis_Width)*180) Picture1_period=PERIOD_M4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*240) Picture1_period=PERIOD_M5; if(EndTime-StartTime>(Picture1_width-Axis_Width)*300) Picture1_period=PERIOD_M6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*360) Picture1_period=PERIOD_M10; if(EndTime-StartTime>(Picture1_width-Axis_Width)*600) Picture1_period=PERIOD_M12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*720) Picture1_period=PERIOD_M15; if(EndTime-StartTime>(Picture1_width-Axis_Width)*900) Picture1_period=PERIOD_M20; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1200) Picture1_period=PERIOD_M30; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1800) Picture1_period=PERIOD_H1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*3600) Picture1_period=PERIOD_H2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*7200) Picture1_period=PERIOD_H3; 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); }
如果该交易并非第一个 - 则利用标绘的余额变动图表创建 "line" 对象。只有当其(至少)一端处于报告周期内时,方可标绘此线。如果两端都在报告周期内 - 线就会“粗”。余额线是绿色的。如果余额超出了最小与最大余额的范围 - 则要对范围进行调整。
else // 如果不是第一笔交易 { // 如果交易在图表周期之内, 绘制余额线, // 并设置余额线的属性 if(time_curr>=StartTime && time_prev<=EndTime) { ObjectCreate(hChart,IntegerToString(i),OBJ_TREND,0,time_prev,balance_prev,time_curr,balance); ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_COLOR,Green); // 如果线的两端都在报告周期之内, // 将是粗线 if(time_prev>=StartTime && time_curr<=EndTime) ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_WIDTH,2); } // 如果余额的新值超过 // 最大值或最小值范围, 必须做调整 if(balance<min_val) min_val=balance; if(balance>max_val) max_val=balance; }
将前一个时间值赋予相应变量。
// 修改前一时间值
time_prev=time_curr;
如果交易尚未处理,则予以处理。
// 如果交易还没有被处理 if(deal_status[i]<127) {
如果此交易为余额变动且处于报告周期内,则相应的字符串会写入报告。交易被标注为已处理。
// 如果交易为余额变动 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE) { // 如果在报告周期内 - 在报告中写下对应描述 if(time_curr>=StartTime && time_curr<=EndTime) FileWrite(file_handle,"<tr><td colspan='9'>Balance:</td><td align='right'>",HistoryDealGetDouble(ticket,DEAL_PROFIT), "</td></tr>"); // 把交易标记为已处理 deal_status[i]=127; }
如果是买入或卖出交易,则检查列表中有没有此工具(symb_list[] 数组)。如果没有,就放上。symb_pointer 变量会指向 symb_list[] 数组中包含当前交易工具名称的元素。
// 如果交易为买入或卖出 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY || HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // 检查列表中有无此交易的资产类型 symb_flag=false; for(j=0;j<symb_total;j++) { if(symb_list[j]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { symb_flag=true; symb_pointer=j; } } // 如果列表中没有此资产 if(symb_flag==false) { symb_list[symb_total]=HistoryDealGetString(ticket,DEAL_SYMBOL); lots_list[symb_total]=0; symb_pointer=symb_total; symb_total++; }
设置 position_StartTime 与 position_EndTime 变量的初始值(存储初始与最终仓位生命周期)。
// 设置交易开始时间的初始值 position_StartTime=time_curr; // 设置交易结束时间的初始值 position_EndTime=time_curr;
in_table_volume、in_table_time、in_table_price、out_table_volume、out_table_time、out_table_price、out_table_swap 和 out_table_profit 变量则会存储将加入更大表格单元格内的表格:进入市场的交易量、时间和价格;退出市场的交易量、时间、价格、掉期和利润。in_table_volume 变量还会存储金融工具的名称和到某图像的链接(对应敞口仓位方向)。为上述所有变量赋初始值。
// 创建报告中的字符串 - 金融工具, 仓位方向, 进入市场的交易量表格起始 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) StringConcatenate(in_table_volume,"<tr><td align='left'>", symb_list[symb_pointer],"</td><td align='center'><img src='buy.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) StringConcatenate(in_table_volume,"<tr><td align='left'>", symb_list[symb_pointer],"</td><td align='center'><img src='sell.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); // 创建进入市场的时间表格起始 in_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // 创建进入市场的价格表格起始 in_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // 创建退出市场的交易量表格起始 out_table_volume="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>"; // 创建退出市场的时间表格起始 out_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // 创建退出市场的价格表格起始 out_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // 创建退出市场的库存费表格起始 out_table_swap="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>"; // 创建退出市场的利润表格起始 out_table_profit="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>";
处理当前仓位平仓前的所有交易。如果之前未处理,现在也都予以处理。
// 处理此仓位从现在开始的所有交易(直到平仓) for(j=i;j<total_deals_number;j++) { // 如果交易还没有被处理 - 处理它 if(deal_status[j]<127) {
选择交易并获取其订单号。
// 选择交易, 取得订单号 ticket=HistoryDealGetTicket(j);
如果交易与敞口仓位的工具相同,则予以处理。获取交易时间。如果交易时间超出了仓位时间的范围,则扩展范围。获取交易量。
// 如果交易工具和仓位工具相匹配, 则进行处理 if(symb_list[symb_pointer]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { // 取得交易时间 time_curr=HistoryDealGetInteger(ticket,DEAL_TIME); // 如果交易时间超出了仓位时间范围 // - 扩展仓位时间 if(time_curr<position_StartTime) position_StartTime=time_curr; if(time_curr>position_EndTime) position_EndTime=time_curr; // 取得交易的交易量 lot_current=HistoryDealGetDouble(ticket,DEAL_VOLUME);
买入与卖出交易分别处理。先从买入交易开始。
// 如果交易是买入 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) {
如果您已建立卖出仓位,则该买入交易就会退出市场。而如果交易量大于已建卖出持仓的交易量,则会成为 in/out。为字符串变量赋予所需值。如果交易已完全处理,则 deal_status[] 数组赋值 127;如果交易为 in/out,则赋值 1,而且必须为其它仓位分析此交易。
// 如果仓位是卖出 - 这将会从市场退出 if(NormalizeDouble(lots_list[symb_pointer],2)<0) { // 如果买入交易量大于开启的卖出持仓交易量, 这就是in/out if(NormalizeDouble(lot_current+lots_list[symb_pointer],2)>0) { // 创建退出市场的交易量表格 - 只指出开启的卖出持仓交易量 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // 把仓位标记为部分处理 deal_status[j]=1; } else { // 如果买入交易量小于等于卖出持仓 - 则是部分或全部平仓 // 创建退出市场的交易量表格 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // 把交易标记为已处理 deal_status[j]=127; } // 创建退出市场的时间表格 StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // 创建退出市场的价格表格 StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // 取得当前交易的库存费 current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // 如果库存费等于0 - 创建退出市场库存费表格的空字符串 if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // 否则创建退出市场的库存费表格的库存费字符串 else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // 取得当前交易的利润 current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // 如果利润为负 (亏损) - 在退出市场的利润表格中显示为红色 if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align=right><SPAN style='COLOR: #EF0000'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); // 否则 - 显示为绿色 else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); }
如果您已建立买入持仓,此交易中的买入就会进入市场(第一个或添加)。如果与此交易对应的 deal_status[] 数组元素值为 1,则意味着完成了 in/out。为字符串变量赋予所需值,并将交易标注为已处理(为相应的 deal_status[] 数组元素赋值 127)。
else // 如果持仓为买入 - 这将是进入市场 { // 如果交易已经被部分处理 (in/out) if(deal_status[j]==1) { // 创建进入市场的交易量表格 (交易量, 是否为 in/out 之后, 写在此处) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // 将会产生交易量改变的补偿, (交易的交易量已经考虑在内) lots_list[symb_pointer]-=lot_current; } // 如果交易还没有被处理 else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // 创建进入市场的时间表格 StringConcatenate(in_table_time,in_table_time,"<tr><td align center>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // 进入市场的价格表格 StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // 把交易标记为已处理 deal_status[j]=127; }
将仓位的交易量更改为当前交易量。如果已平仓(交易量等于零),则停止处理该仓位(利用 j 变量退出循环),并寻找下一个未处理交易(利用 i 变量进入循环)。
// 根据当前资产类型修改仓位交易量, 考虑当前交易的交易量 lots_list[symb_pointer]+=lot_current; // 如果当前资产类型的仓位交易量变成0 - 已经平仓 if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; }
卖出交易亦用类似方式处理,然后再利用 j 变量退出循环。
// 如果交易是卖出 if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // 如果仓位已经建立买入 - 这将会退出市场 if(NormalizeDouble(lots_list[symb_pointer],2)>0) { // 如果卖出交易量大于买入持仓交易量 - 这是 in/out if(NormalizeDouble(lot_current-lots_list[symb_pointer],2)>0) { // 创建退出市场的交易量表格 - 只指明买入持仓交易量 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // 把仓位标记为部分处理 deal_status[j]=1; } else { // 如果卖出交易量大于开启的卖出仓位交易量 - 这是部分或全部平仓 // 创建退出市场的交易量表格 StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // 把交易标记为已处理 deal_status[j]=127; } // 创建退出市场的时间表格 StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // 创建退出市场的价格表格 StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // 取得当前交易的库存费 current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // 如果库存费等于0 - 创建退出市场库存费表格的空字符串 if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // 否则创建退出市场的库存费表格的库存费字符串 else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // 取得当前交易的利润 current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // 如果利润为负 (亏损) - 在退出市场的利润表格中显示为红色 if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'> <SPAN style='COLOR: #EF0000'>",DoubleToString(current_profit,2),"</SPAN></td></tr>"); // 否则 - 显示为绿色 else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); } else // 如果是卖出仓位 - 这将是进入市场 { // 如果交易已经被部分处理 (in/out) if(deal_status[j]==1) { // 创建进入市场的交易量表格 (交易量, 是否为 in/out 之后, 写在此处) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // 将会产生交易量改变的补偿, (交易的交易量已经考虑在内) lots_list[symb_pointer]+=lot_current; } // 如果交易还没有被处理 else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // 创建进入市场的时间表格 StringConcatenate(in_table_time,in_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // 进入市场的价格表格 StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // 把交易标记为已处理 deal_status[j]=127; } // 根据当前资产类型修改仓位交易量, 考虑当前交易的交易量 lots_list[symb_pointer]-=lot_current; // 如果当前资产类型的仓位交易量变成0 - 已经平仓 if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; } } } }
如果开仓时间(至少部分)在报告周期内,则相应的条目就会被输出到 "report.html" 文件。
// 如果仓位时间在报告时间之内- 仓位会打印到报告中 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>");
为 balance_prev 变量赋予余额值。利用 i 变量退出循环。
}
// 修改余额
balance_prev=balance;
}
编写 HTML 文件的结尾(图像链接、居中对齐的结尾、主体部分的结尾、HTML 文档结尾)。关闭 "report.html" 文件。
// 创建 html-文件的末尾 FileWrite(file_handle,"</table><br><br><h2>Balance Chart</h2><img src='picture1.gif'><br><br><br><h2>Price Chart</h2><img src='picture2.gif'></center></body></html>"); // 关闭文件 FileClose(file_handle);
等待图表更新,不超过超时常量中指定的时间。
// 取得当前时间 time_curr=TimeCurrent(); // 等待图表更新 while(SeriesInfoInteger(Symbol(),Picture1_period,SERIES_BARS_COUNT)==0 && TimeCurrent()-time_curr<timeout) Sleep(1000);
设置图表的最大与最小固定值。
// 设置平衡图表的最大值和最小值 (上下边界有10% 缩进) ChartSetDouble(hChart,CHART_FIXED_MAX,max_val+(max_val-min_val)/10); ChartSetDouble(hChart,CHART_FIXED_MIN,min_val-(max_val-min_val)/10);
设置平衡图表的属性。
// 设置平衡图表的属性 ChartSetInteger(hChart,CHART_MODE,CHART_LINE); // 图表为线形图 ChartSetInteger(hChart,CHART_FOREGROUND,false); // 图表在前 ChartSetInteger(hChart,CHART_SHOW_BID_LINE,false); // 隐藏 BID 线 ChartSetInteger(hChart,CHART_COLOR_VOLUME,White); // 交易量和订单水平为白色 ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,White); // 止损和获利水平为白色 ChartSetInteger(hChart,CHART_SHOW_GRID,true); // 显示网格 ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // 网格为浅灰色 ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,false); // 隐藏周期分隔 ChartSetInteger(hChart,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); // 隐藏交易量 ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,White); // 图表为白色 ChartSetInteger(hChart,CHART_SCALE,0); // 最小尺度 ChartSetInteger(hChart,CHART_SCALEFIX,true); // 纵轴固定尺度 ChartSetInteger(hChart,CHART_SHIFT,false); // 没有图表转换 ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // 启用自动滚动 ChartSetString(hChart,CHART_COMMENT,"BALANCE"); // 图表的注释
重绘平衡图表。
// 重绘平衡图表 ChartRedraw(hChart); Sleep(8000);
图表截图(保存 "picture1.gif" 图像)。图表宽度会适应报告周期宽度调整(但由于假期的原因,误差频频出现,图表也变得比余额变动曲线宽),高度按宽度的一半计算。
// 平衡图表截图 ChartScreenShot(hChart,"picture1.gif",(int)(EndTime-StartTime)/PeriodSeconds(Picture1_period), (int)(EndTime-StartTime)/PeriodSeconds(Picture1_period)/2,ALIGN_RIGHT);
删除图表的所有对象并关闭。
// 删除平衡图表的所有对象 ObjectsDeleteAll(hChart); // 关闭图表 ChartClose(hChart);
如果允许通过 FTP 发送文件,则发送下述三个文件:"report.html"、picture1.gif "和" picture2.gif "。
// 如果启用了发布报告 - 通过 FTP 发送 // HTML-文件和两个图片 - 价格图表和平衡图表 if(TerminalInfoInteger(TERMINAL_FTP_ENABLED)) { SendFTP("report.html"); SendFTP("picture1.gif"); SendFTP("picture2.gif"); } }
程序描述到此为止。要通过 FTP 发送文件,您必须调整 MetaTrader 5 设置 - 前往 Tools 菜单,然后是 Options,再打开 Publisher 选项卡(图 4)。
图 4. 通过 FTP 发布报告的选项
在 Options 对话框中,您需要勾选 "Enable" 项,指定账号、FTP 地址、路径、登录名及访问密码。周期性刷新没有影响。
现在,您可以运行脚本。运行后,屏幕上会出现平衡图表,持续几秒后消失。您可以在日志中查找潜在的错误, 看看文件是否已通过 FTP 发送。如果一切正常,则服务器指定文件夹中就会出现三个新文件。如果您把两个带有箭头图像的文件放到那里,WWW 服务器已完成配置且正常运行,则您可以通过网页浏览器打开报告。
2. 以短消息形式向手机发送通知
有些时候,您会离开自己的计算机和其它电子设备,身边只有一部手机。但您却想管理您账户的相关交易,或是监控金融工具的报价。这种情况下,您就可以设置通过短消息向手机发送通知。许多移动运营商(及相关第三方)都提供电子邮件-短信息服务,允许您以信件形式接收发送到某特定电邮地址的消息。
为此,您必须有一个电子邮箱(特别是,您必须知道自己的 SMTP 服务器)。调整您的 MetaTrader 5 设置 - 前往 Tools 菜单,然后是 Options,再打开 Email 选项卡(图 5)。
图 5. 通过电邮发送通知的相关设置
勾选 "Enable" 项,指定 SMTP 服务器地址、登录名与密码、发件人地址 (您的电邮)和收件人地址 - 用于发送短消息的电邮地址(详询您的移动运营商)。如果一切正常,那么,只要您点击 "Test" 按钮,就会发送一条验证信息(详见“日志”)。
如果价格达到某特定水平,最简单的通知方式就是创建一个提醒。为此,打开相应的 "Toolbox" 选项卡,鼠标右击并选择 "Create" (图 6)。
图 6. 创建提醒
于该窗口中勾选 "Enable" 项,选择 "Mail" 动作,选取金融工具、条件,输入条件值并编写消息文本。如果您不想消息重复出现,则在 "Maximum iterations" (最大迭代)中输入 1。填妥所有字段后,点击 OK。
如果我们通过一个 MQL5 程序发送消息,则有更多的可能性。我们会采用 SendMail() 函数。它有两个参数。第一个是标题,第二个则是消息主体。
您可以在交易请求(OrderSend() 函数)后或 Trade 事件处理程序中调用 SendMail() 函数。这样我们就有了下述交易事件的通知 - 进入市场、下订单、平仓。或者,您也可以将 SendMail() 置入 OnTimer() 函数 - 我们就会收到有关当前报价的周期性通知。如有特定交易信号出现 - 指标线相交、价格达到某些线或水平时 - 您就可以安排发送通知了。
我们举几个例子。
在“EA 交易”或脚本中,如果您替换掉
OrderSend(request,result};
换上
string msg_subj,msg_text; if(OrderSend(request,result)) { switch(request.action) { case TRADE_ACTION_DEAL: switch(request.type) { case ORDER_TYPE_BUY: StringConcatenate(msg_text,"Buy ",result.volume," ",request.symbol," 价位 ",result.price,", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_SELL: StringConcatenate(msg_text,"Sell ",result.volume," ",request.symbol," 价位 ",result.price,", 止损=",request.sl,", 获利=",request.tp); break; } break; case TRADE_ACTION_PENDING: switch(request.type) { case ORDER_TYPE_BUY_LIMIT: StringConcatenate(msg_text,"设置BuyLimit ",result.volume," ",request.symbol," 价位 ",request.price,", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_SELL_LIMIT: StringConcatenate(msg_text,"设置 SellLimit ",result.volume," ",request.symbol," 价位 ",request.price,", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_BUY_STOP: StringConcatenate(msg_text,"设置 BuyStop ",result.volume," ",request.symbol," 价位 ",request.price,", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_SELL_STOP: StringConcatenate(msg_text,"设置 SellStop ",result.volume," ",request.symbol," 价位 ",request.price,", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_BUY_STOP_LIMIT: StringConcatenate(msg_text,"设置 BuyStopLimit ",result.volume," ",request.symbol," 价位 ",request.price,", stoplimit=",request.stoplimit, ", 止损=",request.sl,", 获利=",request.tp); break; case ORDER_TYPE_SELL_STOP_LIMIT: StringConcatenate(msg_text,"设置 SellStop ",result.volume," ",request.symbol," 价位 ",request.price,", stoplimit=",request.stoplimit, ", 止损=",request.sl,", 获利=",request.tp); break; } break; case TRADE_ACTION_SLTP: StringConcatenate(msg_text,"修改止损和获利. SL=",request.sl,", TP=",request.tp); break; case TRADE_ACTION_MODIFY: StringConcatenate(msg_text,"修改订单",result.price,", 止损=",request.sl,", 获利=",request.tp); break; case TRADE_ACTION_REMOVE: msg_text="删除订单"; break; } } else msg_text="错误!"; StringConcatenate(msg_subj,AccountInfoInteger(ACCOUNT_LOGIN),"-",AccountInfoString(ACCOUNT_COMPANY)); SendMail(msg_subj,msg_text);
则 OrderSend() 函数会在交易请求后,利用 SendMail() 函数发送一条消息。其中包含交易账号、经纪人名称以及实施动作(买入、卖出、下达挂单、订单修改或删除)的相关信息,如下所示:
59181-MetaQuotes Software Corp. Buy 0.1 EURUSD at price 1.23809, SL=1.2345, TP=1.2415
而且,如果是在任何“EA 交易”或 OnInit() 的主体内的指标中,您就要利用 EventSetTimer() 函数启动计时器 (它只有一个参数 - 以秒计的计时器周期):
void OnInit() { EventSetTimer(3600); }
在 OnDeinit() 中,不要忘了用 EventKillTimer() 将其关闭:
void OnDeinit(const int reason) { EventKillTimer(); }
而在 OnTimer() 中,则是利用 SendMail() 发送消息:
void OnTimer() { SendMail(Symbol(),DoubleToString(SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits)); }
之后,您就会收到带有指定周期的当前金融工具价格的相关消息。
总结
本文讲述的是,如何利用 MQL5 程序创建一个 HTML 与图像文件,以及如何通过 FTP 将其上传到 WWW 服务器。还介绍了以短消息形式向您的手机发送通知的配置方式。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/61
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



开头没有图片,只有标题。
开头没有图片,只有标题。
丹尼斯,感谢你的分享,教我们如何使用交易历史,以及如何用 html 创建报告。
做得非常好。
我正在尝试将它与我的 EA 集成,但我还有一些工作要做,或者在需要时使用修改后的脚本。
再次感谢您!
马塞洛