外汇EA编写教程:交易报告及短信通知的创建和发布

简介

本文会讲述如何生成一份 HTML 文件格式的交易结果报告(利用“EA 交易”、指标或脚本),并通过 FTP 将其上传到 WWW 服务器。我们还会考虑以短消息形式向手机发送交易事件通知。

想要更自如地阅读本文内容,建议读者熟悉 HTML (超文本标记语言)知识。

要实施报告上传,我们需要一个可以通过 FTP 接受数据的 WWW 服务器(可以是任何计算机)。而要实现接收短消息形式的交易事件通知,我们需要一个电子邮箱 – 短消息的网关(大多数移动运营商和第三方组织都提供该服务)。

1. 创建报告并通过 FTP 发送

我们来创建一个 MQL5 程序,并利用它来生成一份交易报告,再将报告通过 FTP 协议发送出去。首先,我们将其制作为脚本。将来我们能用它作为一个成品块,插入到“EA 交易”和指标中。比如说,在“EA 交易”中,您可以将此程序块作为 TradeTimer 事件处理程序,于交易请求后运行此块,或是为 ChartEvent 事件设定一些动作。而在指标中,您则可以将此块加入到 TimerChartEvent 事件处理程序中。

程序创建的报告示例,如图 1、2、3 所示。或者,您也可以通过本文末尾处的链接下载该报告。

图 1. 报告示例 - 交易与仓位表

图 1. 报告示例 – 交易与仓位表

图 2. 报告示例 - 平衡图.

图 2. 报告示例 – 平衡图

 图 3. 报告示例 - 当前工具的价格图表

图 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 发布报告的选项

图 4. 通过 FTP 发布报告的选项

在 Options 对话框中,您需要勾选 “Enable” 项,指定账号、FTP 地址、路径、登录名及访问密码。周期性刷新没有影响。

现在,您可以运行脚本。运行后,屏幕上会出现平衡图表,持续几秒后消失。您可以在日志中查找潜在的错误, 看看文件是否已通过 FTP 发送。如果一切正常,则服务器指定文件夹中就会出现三个新文件。如果您把两个带有箭头图像的文件放到那里,WWW 服务器已完成配置且正常运行,则您可以通过网页浏览器打开报告。

2. 以短消息形式向手机发送通知

有些时候,您会离开自己的计算机和其它电子设备,身边只有一部手机。但您却想管理您账户的相关交易,或是监控金融工具的报价。这种情况下,您就可以设置通过短消息向手机发送通知。许多移动运营商(及相关第三方)都提供电子邮件-短信息服务,允许您以信件形式接收发送到某特定电邮地址的消息。

为此,您必须有一个电子邮箱(特别是,您必须知道自己的 SMTP 服务器)。调整您的 MetaTrader 5 设置 – 前往 Tools 菜单,然后是 Options,再打开 Email 选项卡(图 5)。

图 5. 通过电邮发送通知的相关设置

图 5. 通过电邮发送通知的相关设置

勾选 “Enable” 项,指定 SMTP 服务器地址、登录名与密码、发件人地址 (您的电邮)和收件人地址 – 用于发送短消息的电邮地址(详询您的移动运营商)。如果一切正常,那么,只要您点击 “Test” 按钮,就会发送一条验证信息(详见“日志”)。

如果价格达到某特定水平,最简单的通知方式就是创建一个提醒。为此,打开相应的 “Toolbox” 选项卡,鼠标右击并选择 “Create” (图 6)。

图 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 Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/61

附加的文件 |

report.zip
(33.08 KB)
sendreport_en.mq5
(52.91 KB)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投