利用 MQL5 和 MQL4 实现的选择和导航工具: 把数据添加到图表中

Roman Klymenko | 31 五月, 2019

简介

在前面的文章中我们已经做了很多 (使用 MQL5 和 MQL4 开发的选择与导航工具: 增加自动模式搜索和显示侦测到的交易品种, 利用 MQL5 和 MQL4 实现的选择和导航实用程序:添加"HOMEWORK"选项卡并保存图形对象, 运用 MQL5 和 MQL4 开发品种选择和导航实用程序). 但这对于高质量的日内交易仍然是不够的。在本文中,我们将添加新的功能,使我们能够找到合适的市场进入/退出点。

每个功能项目将在单独的章节中进行描述。首先,我们将考虑它的目标。然后,我们将分析一个函数或一个代码,以防您希望在项目中实现相同的功能。


隐藏今天的趋势与昨天不同的交易品种

输入: 如果今天的趋势!=昨天就隐藏
范围: 排序设置

许多日内交易大师建议交易股票,其今天的趋势与昨天的趋势相同。简单地说,如果股价昨天上涨,那么今天也会上涨,这意味着你今天的交易只能买入,反之亦然。

以下函数允许您对这些交易品种进行排序:

/*
如果今天的趋势与昨天的不同,就返回 true
symname - 使用的交易品种名称
*/
bool check_no_trend(string symname){
   MqlRates rates2[];
   ArraySetAsSeries(rates2, true);
   if(CopyRates(symname, PERIOD_D1, 0, 2, rates2)==2){
      if( rates2[0].close>rates2[0].open && rates2[1].close>rates2[1].open ){
         return false;
      }
      if( rates2[0].close<rates2[0].open && rates2[1].close<rates2[1].open ){
         return false;
      }
   }
   return true;
}


显示开盘时间

输入: 显示一个交易日的开始 (在 <D1 的时段)
范围: 图表设置

交易时段的开始是指股市开市(如果我们谈论的是股票),例如,美国股市下午4:30,欧洲和俄罗斯股市上午10:00。在外汇交易中,让我们把午夜作为交易时段的开始。

例如,在较低的时间框架(如M5)上,可能很难定义当前交易日的开始时间,以便一眼就能看到今天的股票是在增长还是在下跌。因此,我们将添加一个设置,允许我们看到一个交易品种上当前交易日开始的柱。

显示交易日开始的函数:

/*
在图表商显示交易日的开始
currChart - 图表 id
day - 日期编号 (0 - 当前)
*/
void showNdays(long currChart, int day){
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   if(CopyRates(ChartSymbol(currChart), PERIOD_D1, day, 1, rates)>0){
      ObjectCreate(currChart, exprefix+"_Nday", OBJ_VLINE, 0, rates[0].time, 0);
      ObjectSetInteger(currChart,exprefix+"_Nday",OBJPROP_STYLE,STYLE_DOT);
      ObjectSetInteger(currChart,exprefix+"_Nday",OBJPROP_RAY,true); 
      ObjectSetInteger(currChart,exprefix+"_Nday",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_Nday",OBJPROP_BACK,true); 
   }
}

在图表上,这看起来像一条垂直的红线:

显示开盘时间

MetaTrader 中已经存在类似的功能。此外,您不仅可以看到最后一个交易日的开始,还可以看到每个交易日的一般开始。如果要这样做, 就在图表属性中选中 "显示时段分隔" 复选框。在实际实践中,在某些时间段内,这种功能是多余的,会使图表杂乱无章,不利于分析。例如:

显示时段分隔


显示昨天和前天的最低价和最高价

输入: 显示昨天的最低价和最高价以及显示前天的最低价和最高价
范围: 图表设置

在日内交易时,昨天和前天的高点和低点经常被用作支撑和阻力水平。价格经常触及昨天或前天的高点并反弹下跌。根据我的经验,在70%的情况下,价格不会打破这样的高点/低点。

因此,这些水平不应忽视。

以下函数用于显示所选日期的最低价和最高价:

/*
在图表上显示指定日的最低价和最高价
currChart - 图表 ID
day - 显示日期的索引
*/
void showHOBeforeDay(long currChart, int day=1){
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   
   color curColor=clrAquamarine;
   switch(day){
      case 2:
         curColor=clrOlive;
         break;
   }
   
   if(CopyRates(ChartSymbol(currChart), PERIOD_D1, day, 1, rates)>0){
      ObjectCreate(currChart, exprefix+"_beforeday_high_line"+(string) day, OBJ_HLINE, 0, 0, rates[0].high);
      ObjectSetInteger(currChart,exprefix+"_beforeday_high_line"+(string) day,OBJPROP_COLOR,curColor); 
      ObjectSetInteger(currChart,exprefix+"_beforeday_high_line"+(string) day,OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_beforeday_high_line"+(string) day,OBJPROP_BACK,true); 
      
      ObjectCreate(currChart, exprefix+"_beforeday_low_line"+(string) day, OBJ_HLINE, 0, 0, rates[0].low);
      ObjectSetInteger(currChart,exprefix+"_beforeday_low_line"+(string) day,OBJPROP_COLOR,curColor); 
      ObjectSetInteger(currChart,exprefix+"_beforeday_low_line"+(string) day,OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_beforeday_low_line"+(string) day,OBJPROP_BACK,true); 
   }
}

昨天的高低线显示为明亮的水平线,前天的高低线显示为苔藓绿线:

显示昨天的/前天的低点和高点


显示最近的整数价格

输入: 显示最近的整数价格 (时段 < 1 天)
范围: 图表设置

整数价格是另一个自然的支撑/阻力水平。这些是以0.00或0.50结尾的价格。许多交易员只在接近整数水平时寻找交易机会。

.25 和 .75. 级别也可能被认为是整数的,但它们不太可靠,因此我们不会跟踪它们,以免使图表混乱。

下面的函数用于显示整数价格:

/*
显示当前价格最近的三个整数水平
currChart - 图表 id
*/
void showRoundPrice(long currChart){
   string name=ChartSymbol(currChart);
   int tmpPrice;
   
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   if(CopyRates(name, PERIOD_D1, 0, 1, rates)==1){
      switch((int) SymbolInfoInteger(name, SYMBOL_DIGITS)){
         case 0:
            break;
         case 2:
         case 3:
         case 5:
            tmpPrice=(int) rates[0].close;

            ObjectCreate(currChart, exprefix+"_round_line_00_0", OBJ_HLINE, 0, 0, (double) tmpPrice-1 );
            ObjectSetInteger(currChart,exprefix+"_round_line_00_0",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_0",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_0",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_0",OBJPROP_BACK,true); 

            ObjectCreate(currChart, exprefix+"_round_line_05_0", OBJ_HLINE, 0, 0, (double) tmpPrice-0.5 );
            ObjectSetInteger(currChart,exprefix+"_round_line_05_0",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_0",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_0",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_0",OBJPROP_BACK,true); 
            
            ObjectCreate(currChart, exprefix+"_round_line_00_1", OBJ_HLINE, 0, 0, (double) tmpPrice );
            ObjectSetInteger(currChart,exprefix+"_round_line_00_1",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_1",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_1",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_1",OBJPROP_BACK,true); 

            ObjectCreate(currChart, exprefix+"_round_line_05_1", OBJ_HLINE, 0, 0, (double) tmpPrice+0.5 );
            ObjectSetInteger(currChart,exprefix+"_round_line_05_1",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_1",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_1",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_1",OBJPROP_BACK,true); 

            ObjectCreate(currChart, exprefix+"_round_line_00_2", OBJ_HLINE, 0, 0, (double) tmpPrice+1 );
            ObjectSetInteger(currChart,exprefix+"_round_line_00_2",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_2",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_2",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_00_2",OBJPROP_BACK,true); 

            ObjectCreate(currChart, exprefix+"_round_line_05_2", OBJ_HLINE, 0, 0, (double) tmpPrice+1.5 );
            ObjectSetInteger(currChart,exprefix+"_round_line_05_2",OBJPROP_COLOR,clrCadetBlue); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_2",OBJPROP_STYLE, STYLE_DASH); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_2",OBJPROP_SELECTABLE,false); 
            ObjectSetInteger(currChart,exprefix+"_round_line_05_2",OBJPROP_BACK,true); 
            
            break;
      }
   }
}

在图表上,整数水平线用虚线表示:

显示最近的整数价格


显示小时结束

输入参数: 显示小时结束 (时段 < 1小时)
范围: 图表设置

继续讨论自然支持/阻力水平,我们也可以提到小时结束水平。人们相信,小时收盘价在较小的时间范围内也可能是一个支撑/阻力水平,尽管我从未在实际交易中注意到这一点。不过,让我们实现至少在过去四个小时内显示这个参数的能力。

在图表上突出显示小时结束的函数:

/*
在图表上显示最后四小时
currChart - 图表 id
*/
void showCloseHour(long currChart){
   MqlDateTime tmpTime;
   int tmpOffset=0;
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   if(CopyRates(ChartSymbol(currChart), PERIOD_M30, 0, 9, rates)>0){
      tmpOffset=0;
      TimeToStruct(rates[tmpOffset].time, tmpTime);
      if(tmpTime.min!=0){
         tmpOffset++;
      }
      ObjectCreate(currChart, exprefix+"_hour_0", OBJ_VLINE, 0, rates[tmpOffset].time, 0);
      ObjectSetInteger(currChart,exprefix+"_hour_0",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_hour_0",OBJPROP_STYLE,STYLE_DOT);
      ObjectSetInteger(currChart,exprefix+"_hour_0",OBJPROP_RAY,true); 
      ObjectSetInteger(currChart,exprefix+"_hour_0",OBJPROP_COLOR,clrYellow); 
      ObjectSetInteger(currChart,exprefix+"_hour_0",OBJPROP_BACK,true); 

      tmpOffset++;
      TimeToStruct(rates[tmpOffset].time, tmpTime);
      if(tmpTime.min!=0){
         tmpOffset++;
      }
      ObjectCreate(currChart, exprefix+"_hour_1", OBJ_VLINE, 0, rates[tmpOffset].time, 0);
      ObjectSetInteger(currChart,exprefix+"_hour_1",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_hour_1",OBJPROP_STYLE,STYLE_DOT);
      ObjectSetInteger(currChart,exprefix+"_hour_1",OBJPROP_RAY,true); 
      ObjectSetInteger(currChart,exprefix+"_hour_1",OBJPROP_COLOR,clrYellow); 
      ObjectSetInteger(currChart,exprefix+"_hour_1",OBJPROP_BACK,true); 

      tmpOffset++;
      TimeToStruct(rates[tmpOffset].time, tmpTime);
      if(tmpTime.min!=0){
         tmpOffset++;
      }
      ObjectCreate(currChart, exprefix+"_hour_2", OBJ_VLINE, 0, rates[tmpOffset].time, 0);
      ObjectSetInteger(currChart,exprefix+"_hour_2",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_hour_2",OBJPROP_STYLE,STYLE_DOT);
      ObjectSetInteger(currChart,exprefix+"_hour_2",OBJPROP_RAY,true); 
      ObjectSetInteger(currChart,exprefix+"_hour_2",OBJPROP_COLOR,clrYellow); 
      ObjectSetInteger(currChart,exprefix+"_hour_2",OBJPROP_BACK,true); 

      tmpOffset++;
      TimeToStruct(rates[tmpOffset].time, tmpTime);
      if(tmpTime.min!=0){
         tmpOffset++;
      }
      ObjectCreate(currChart, exprefix+"_hour_3", OBJ_VLINE, 0, rates[tmpOffset].time, 0);
      ObjectSetInteger(currChart,exprefix+"_hour_3",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_hour_3",OBJPROP_STYLE,STYLE_DOT);
      ObjectSetInteger(currChart,exprefix+"_hour_3",OBJPROP_RAY,true); 
      ObjectSetInteger(currChart,exprefix+"_hour_3",OBJPROP_COLOR,clrYellow); 
      ObjectSetInteger(currChart,exprefix+"_hour_3",OBJPROP_BACK,true); 
      
   }
}

在图表上,最近四个小时的结束显示为垂直黄色线:

显示小时结束


显示一年的最高和最低价格

Input: 显示一年的最高/最低价
范围: 图表设置

一年的最高和最低价格也很受交易员欢迎。也在图表上显示它们。

以下函数用于显示一年内的极端价格:

/*
在图表上显示去年的最高和最低价格。
currChart - 图表 id.
*/
void showMaxPrice(long currChart){
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   if(CopyRates(ChartSymbol(currChart), PERIOD_MN1, 0, 12, rates)==12){
      double maxPrice=0;
      double minPrice=0;
      
      
      for(int j=0; j<12; j++){
         if( maxPrice==0 || maxPrice<rates[j].high ){
            maxPrice=rates[j].high;
         }
         if( minPrice==0 || minPrice>rates[j].low ){
            minPrice=rates[j].low;
         }
      }
      
      ObjectCreate(currChart, exprefix+"_maxprice_line", OBJ_HLINE, 0, 0, maxPrice);
      ObjectSetInteger(currChart,exprefix+"_maxprice_line",OBJPROP_COLOR,clrMagenta); 
      ObjectSetInteger(currChart,exprefix+"_maxprice_line",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_maxprice_line",OBJPROP_BACK,true); 

      ObjectCreate(currChart, exprefix+"_minprice_line", OBJ_HLINE, 0, 0, minPrice);
      ObjectSetInteger(currChart,exprefix+"_minprice_line",OBJPROP_COLOR,clrMagenta); 
      ObjectSetInteger(currChart,exprefix+"_minprice_line",OBJPROP_SELECTABLE,false); 
      ObjectSetInteger(currChart,exprefix+"_minprice_line",OBJPROP_BACK,true); 
      
      
   }
}

过去12个月的最高和最低价格显示为紫色横线:

显示一年的最高/最低价格


当打开图表时显示点差

输入: 当打开图表时显示点差
范围: 图表设置

如果使用小止损,当前交易品种的点差是进入交易的决定性因素之一。因此,在打开图表时了解它是很重要的。所以,让我们在图表中添加更多数据。

请记住,我们只在打开图表时显示点差,它可能会在几分钟内改变。点差水平不会实时显示。它仅用于计算交易品种上的当前点差范围。

下面的小函数返回点差:

/*
返回具有指定交易品种的当前点差的字符串。
symname - 交易品种名称
*/
string getmespread_symbol(string symname){
   double curSpread=SymbolInfoDouble(symname, SYMBOL_ASK)-SymbolInfoDouble(symname, SYMBOL_BID);
   return "Spread: "+(string) DoubleToString(curSpread, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+" ("+DoubleToString(curSpread/SymbolInfoDouble(symname, SYMBOL_POINT), 0)+" p)";
}

点差显示在打开图表的注释中,以及其他附加数据:

显示点差

根据名称在图表上应用模板

输入: 根据名称在图表上应用模板
范围: 图表设置

在MetaTrader中,模板用于快速应用首选设置、EA,更重要的是,将具有首选参数的指标应用于图表。

然而,“快速”是一个松散的概念。要将模板应用于图表,请在图表的上下文菜单中选择模板/“模板名称”。但是,如果一个指标,例如移动平均线,对你做出决定很重要,并且你想在你打开的每个图表上看到它,同时每天使用数百种工具,该怎么办?你真的要点击它们中的每一个吗?

对于每个打开的图表,自动应用带有一组指标的特定模板要容易得多。所以让我们实现这个特性。

要将模板应用于图表,请使用在 MQL5 和 MQL4 中同时工作的ChartApplyTemplate命令:

      ChartApplyTemplate(curChartID[ArraySize(curChartID)-1], allApplyTemplate+".tpl");


显示 Gerchik's ATR

输入: 显示 Gerchik's ATR
范围: 图表设置

ATR是交易品种在一定时间内的平均日移动量,是确定交易品种作用范围的重要参数。它有多种不同用途,

例如,它有助于决定在日内交易期间是否值得进行交易。如果你想捕捉100点的移动,而交易品种在一天内的平均范围是50点,你的机会很小,最好不要进入交易。

也可以认为,如果一个交易品种已经在一个方向上通过了其每日ATR的80%以上,那么在同一个方向上进行交易是没有意义的,并且您只能在相反的方向上工作。

Gerchik's ATR 与传统的ATR不同的是,只考虑了异常的柱。异常柱是指比平均柱大2倍或小0.3倍的柱。

ATR通常计算5或3天。

以下函数显示了 Gerchik's ATR:

/*
为指定交易品种返回表示 Gerchik ATR 的字符串:
symname - 交易品种名称
*/
string getmeatr_symbol(string symname){
   string msg="";
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   double atr=0;
   double pre_atr=0;
   int curDigits=(int) SymbolInfoInteger(symname, SYMBOL_DIGITS);
   string currencyS=SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT);
   int count=0;
   int copied=CopyRates(symname, PERIOD_D1, 0, 8, rates);
   if(copied>1){
      for(int j=1; j<copied; j++){
         pre_atr+=rates[j].high-rates[j].low;
      }
      pre_atr/=(copied-1);
   }
   if(pre_atr>0){
      for(int j=1; j<copied; j++){
         if( rates[j].high-rates[j].low > pre_atr*2 ) continue;
         if( rates[j].high-rates[j].low < pre_atr*0.3 ) continue;
               
         if( ++count > 5 ){
            break;
         }
               
         atr+=rates[j].high-rates[j].low;
      }
      if( count > 5 ){
         count=5;
      }
      atr= NormalizeDouble(atr/count, curDigits );
   }
   if(atr>0){
      StringAdd(msg, "ATR: "+(string) DoubleToString(atr, curDigits)+" "+currencyS+", today: "+DoubleToString(rates[0].high-rates[0].low, curDigits)+" "+currencyS+" ("+(string) (int) (((rates[0].high-rates[0].low)/atr)*100) +"%)");
   }
   
   return msg;
}

该实用程序在图表注释中显示ATR,如前一节中描述的点差。ATR 可以在之前的屏幕截图中看到。


导向交易品种的显示方向

输入: 在 #N 显示方向
范围: 显示额外数据

从我在本文中已经提到的情况来看,日内交易是一项非常复杂的工作,因为有许多事情需要跟踪和记住。事实证明,这并不是全部。

许多交易者建议在进入交易前先查看所谓的指导交易品种的当前方向。

例如,如果你在美国股票市场交易,那么你需要:

  • 美国主要指数,至少是道琼斯或SP500;
  • 其余国家的一个指数:中国、欧洲、日本;
  • 各细分市场的大型股公司:摩根大通银行业、苹果科技、埃克森美孚能源、IBM等。

您还可以跟踪石油、黄金和美元的报价。

最好是所有的引导符号都朝一个方向,正如你可能猜测的那样,最好只在导向符号所处的方向上进行交易。

由于不同的经纪商可能对同一个工具有不同的名称,因此无法以编程方式指定要监控的工具。但是我们可以添加字符串类型的传入参数,使用户可以按照代理的名称设置交易品种的标记符。该实用程序有七个参数。

以下函数返回指定符号的方向:

/*
通过指定的交易品种返回具有价格移动方向的字符串:
symname - 交易品种名称
show - 字符串中是否应显示交易品种名称标签
*/
string getmeinfo_symbol(string symname, bool show=true){
   MqlRates rates2[];
   ArraySetAsSeries(rates2, true);
   string msg="";

   if(CopyRates(symname, PERIOD_D1, 0, 1, rates2)>0){
      if(show){
         StringAdd(msg, (string) symname+": ");
      }
      StringAdd(msg, "D1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2) +"% (+"+DoubleToString(rates2[0].close-rates2[0].open, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2) +"% (-"+DoubleToString(rates2[0].open-rates2[0].close, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   if(CopyRates(symname, PERIOD_H1, 0, 1, rates2)>0){
      StringAdd(msg, ", H1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2)+"% (+"+DoubleToString(rates2[0].close-rates2[0].open, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2)+"% (-"+DoubleToString(rates2[0].open-rates2[0].close, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   
   return msg;
}

方向显示在启动实用程序的图表注释中。这看起来如下.

导向交易品种的显示方向

请注意,导向交易品种的运动方向不会自动更新。要更新它们上的数据,请切换到任何其他选项卡或按 R. 然而,这也可以通过新的输入来简化。


自动更新实用程序选项卡

输入: 设置更新一个打开的选项卡的秒数
范围: 显示额外数据

如果您希望当前打开的选项卡的内容自动更新(而不仅仅是通过按R来更新),只需设置秒数,然后进行更新。超过0的值提示实用程序在指定的秒数过后更新选项卡。这并不意味着选项卡将严格每N秒更新一次。相反,它意味着标签将在 N加上另外一两秒后更新。

实现该功能的代码位于OnTimer函数内部,非常简单:

   // 指定秒数后更新选项卡的内容
   if( autoUpdateAfter>0 && TimeCurrent()-lastUpd >= autoUpdateAfter ){
      start_symbols();
      lastUpd=TimeCurrent();
   }

在实用程序操作开始时,我们通过OnInit中的当前时间初始化了变量值LastUpd

显示股票的财务数据

输入: 显示公司信息
范围: 外部数据

如果您在购买或出售股票之前遵循公司的基本参数,您可能希望这些参数直接显示在MetaTrader窗口中,以避免浏览不同的网站。

让我们尝试实现这个功能。为了简单起见,只要使用实用程序打开图表,我们就会在终端的“专家”选项卡中显示参数。

当然,财务参数本身并不是凭空得出的。起初,我想将它们直接“硬编码”到EA中,但是内存不足的错误干扰了我的计划。因此,所有财务参数都单独存储在文章所附的文件夹中。要使此功能正常工作,请下载存档文件,将其解包,然后将finder文件夹放在终端的Files目录下。

要查找Files目录,请打开 MetaTrader中的 文件菜单,然后单击打开数据文件夹。在新打开的资源管理器窗口中,转到MQL4MQL5文件夹。

截至本文撰写之日,并且可能在接下来的几个月内,这些财务参数是相关的。如果您以后需要更多相关数据,请给我写一封私信。

但让我们回到数据输出函数。该函数从公司的股票代码中删除多余的元素,即前缀,以及一些经纪商添加的.us和.eu后缀。如果您的代理向分时器添加了其他内容,请向实用程序代码添加您自己的过滤器,以删除多余的字符。

如果在finder文件夹中找不到必要的分时器数据,则通过公司名称执行搜索。

void printTicker(string ticker, string name){
   bool isYes=false;
   int filehandle;
   
   // 从分时器中删除多余的后缀和前缀
   // 并将其转换为小写
   StringToLower(ticker);
   StringReplace(ticker, "#", "");
   StringReplace(ticker, ".us", "");
   StringReplace(ticker, ".eu", "");
   
   // 将公司名称转换为小写
   StringToLower(name);
   
   ResetLastError(); 
   // 如果“finder”文件夹中包含公司的数据,则显示该文件夹。
   if( FileIsExist("finder\\"+ticker+".dat") ){
      isYes=true;
      filehandle=FileOpen("finder\\"+ticker+".dat",FILE_READ|FILE_TXT); 
      if(filehandle!=INVALID_HANDLE){ 
         int str_size; 
         string str; 
         Print("----------------");
         while(!FileIsEnding(filehandle)){ 
            str_size=FileReadInteger(filehandle,INT_VALUE); 
            str=FileReadString(filehandle,str_size); 
            Print(str);
         }
         FileClose(filehandle); 
      }
   }else if( FileIsExist("finder\\"+name+".dat") ){
      isYes=true;
      filehandle=FileOpen("finder\\"+name+".dat",FILE_READ|FILE_TXT); 
      if(filehandle!=INVALID_HANDLE){ 
         int str_size; 
         string str; 
         Print("----------------");
         while(!FileIsEnding(filehandle)){ 
            str_size=FileReadInteger(filehandle,INT_VALUE); 
            str=FileReadString(filehandle,str_size); 
            Print(str);
        }
         FileClose(filehandle); 
      }
   }
   
   // 否则,通知未找到数据
   if(!isYes){
      Print("没有交易品种数据");
   }
}

因此,使用该实用程序打开图表时,公司信息显示在专家选项卡中。大致如下:

公司财务信息


显示今天的利润

输入: 显示今天的利润
范围: 显示额外数据

最后,让我们添加一个参数,允许我们在适当的时间内停止我们的日常交易活动,这是一天中收到的利润或损失金额,以及每天的交易总数,其中包括盈利和亏损交易的数量(按上述顺序显示在图表上)。

您可以使用下面显示的getmetoday_profit()获得MQL4的当前利润。如果是 MQL5, 这个函数还不够。

在MQL5中,我们首先检查关闭仓位上的数据是否应该更新。要执行此操作,请使用全局声明的needHistory变量。在<I1>OnInit()中,此变量获取“true”的值。因此,在getmetoday_profit()函数第一次运行,交易数据从历史中上传,之后,needHistory的值变为“false”。

接下来,我们使用标准的 MQL5 OnTrade()函数在每次我们的一个未结头寸关闭时再次上载交易历史记录。

结果,我们将有下面的代码:

#ifdef __MQL5__ 
   void OnTrade(){
      if(orders_total==PositionsTotal()){
         return;
      }
      if(orders_total>PositionsTotal()){
         needHistory=true;
      }
      orders_total=(ushort) PositionsTotal();
   }
#endif 
/*
返回包含今天利润/亏损的字符串。
*/
string getmetoday_profit(){
   string msg="";
   double curProfit=0;
   int tradeAll=0;
   int tradeMinus=0;
   int tradePlus=0;
   
   MqlDateTime curD;
   TimeCurrent(curD);
   curD.hour=0;
   curD.min=0;
   curD.sec=0;
   
   #ifdef __MQL5__ 
      if( needHistory ){
         HistorySelect(StructToTime(curD),TimeCurrent()); 
      }
      uint totalHistory=HistoryDealsTotal();
      double currHistory=0;
      ulong ticket_history;
      for(uint j=0;j<totalHistory;j++){
         if((ticket_history=HistoryDealGetTicket(j))>0 ){
            double profitHistory=HistoryDealGetDouble(ticket_history,DEAL_PROFIT);
            profitHistory+=HistoryDealGetDouble(ticket_history,DEAL_COMMISSION);
            profitHistory+=HistoryDealGetDouble(ticket_history,DEAL_SWAP);
            if(profitHistory!=0){
               currHistory+=profitHistory;
            }
         }
      }
         
   #else 
      int cntMyPos=OrdersHistoryTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_HISTORY)==false) continue;
            
            if( OrderCloseTime()<StructToTime(curD) ){ continue; }
            
            double tmpProfit=OrderProfit();
            tmpProfit+=OrderSwap();
            tmpProfit+=OrderCommission();
            
            tradeAll++;
            if(tmpProfit>0){
               tradePlus++;
            }else if(tmpProfit<0){
               tradeMinus++;
            }
            curProfit+=tmpProfit;
         }
      }
   #endif 
   
   if(tradeAll>0){
      StringAdd(msg, "Today: "+DoubleToString(curProfit, 2)+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(string) tradeAll+";+"+(string) tradePlus+";-"+(string) tradeMinus+")");
   }
   
   return msg;
}

利润也会显示在启动实用程序的图表上。看起来如下:

显示今天的利润

记住,当天的利润不是自动重新计算的。要更新当前的利润数据,请打开另一个选项卡或按R


结论

这很可能是本系列的最后一篇文章,我希望由此开发的工具对您有用。

但是如果你缺少一些功能,请给我写信。我将继续改进实用程序,以防有足够的功能缺失来撰写新文章。

总之,让我们总结并简要回顾一下我们在本系列文章中实现的功能。

启动实用程序后,我们可以访问满足我们条件的工具列表。在列表的上方,我们可以看到 All, LONG, SHORTRange 选项卡以及自动排序选项卡的按钮。. All 选项卡是默认打开的:

运行工具的图表

在图表注释中,您可以看到以 update 开始的一行,这一行包含最后页面内容更新的时间,按下 R 键可以人工更新当前选项卡。此外,您可以在实用程序设置中指定当前打开的选项卡内容更新周期(以秒为单位)。

说到实用程序设置…如果我们考虑这些设置,就更容易理解实用程序的功能。

All选项卡中显示的交易品种列表取决于过滤设置组:

过滤设置组

如果我们单击实用程序显示的交易品种按钮,将打开具有额外数据的适当交易品种图表:

使用工具打开的交易品种图表

在图表上我们首先注意到的是导航按钮,它们允许我们从一个按实用程序排序的交易品种移动到另一个交易品种。另外,这些按钮可以让我们把交易品种添加到 "homework" 选项卡 (LONG, SHORTRange) 或者从那里把它们移除。

此外,图表注释还具有各种交易品种数据。此外,还有各种水平线。

紫色线表示年份的最高价/最低价,

红色代表自定义级别。该实用程序可以保存和恢复为交易品种绘制的所有自定义级别。

如前所述,绿线代表昨天开盘/收盘价的前一天。屏幕截图上的最后一条线表示昨天的开盘价/收盘价。

图表设置组 group 定义要在交易品种图表上显示的线和数据:

图表设置组

时间框架和图表显示比例也可自定义。这是分别为All选项卡、“homework”选项卡和一些自动排序选项卡完成的。

All选项卡的时间框架是在附加数据中设置的。

附加数据组设置

All选项卡上所有的交易品种按钮,默认使用 D1 时段。让我们把它改为 M15:

M15 时段的交易品种图表

这使我们在图表上看到更多的线,因为这些线只在少于1天的时间段内显示。

红色竖线显示当前交易时段开始时的柱,便于直观地确定今天交易品种是上涨还是下跌。

虚线水平线表示四舍五入水平,即以0.00或0.50结尾的价格(对于小数点后两位的报价)或以0.x1000/0.xx500结尾的价格(对于小数点后四位和五位的报价)(如此处所示)。

但让我们回到设置,自动排序选项卡也有自己的设置。全局选项卡位于附加选项卡中,此外,还有单独的组,其中包含一些选项卡的设置:

自动排序选项卡设置

最后一组设置是在新窗口中打开图表外部数据

在新窗口和外部数据设置组中打开图表

在新窗口中打开图表中的设置会影响单击新窗口时打开图表的周期和比例。

有关实用程序功能的更多详细信息,可以在本系列的前几篇文章中找到。