
使用 MQL5 和 MQL4 开发的选择与导航工具: 增加自动模式搜索和显示侦测到的交易品种
简介
许多交易策略在寻找各种市场进入模式时需要不断分析交易品种,虽然搜索和分析交易品种对于深入了解市场可能很有用,但有时,您可能希望简单地向您显示具有必要参数的交易品种列表。在本文中,我们将尝试开发这样的工具用于一些日内交易形态。
更具体地说,今天我们将通过添加根据特定参数对交易品种进行自动挑选来扩展实用程序的功能。为此,我们将创建一系列选项卡,搜索当前具有特定交易形态的交易品种:抛物线曲线、空中水平(平盘范围)、跳空等。我们还将学习如何添加自定义选项卡,当然前提是您了解MQL编程的基础知识。
如前一篇文章所述,我们的实用程序将同时在MQL4和MQL5中工作。展望未来,我应该说,在MQL5中打开自动挑选选项卡比在MQL4中慢,当请求的交易品种没有达到所需深度的历史记录时,就会发生这种情况。在这种情况下,MetaTrader 5从交易服务器请求历史记录,并构建所需的时间框架。
因此,如果在单击选项卡时什么都没有发生,或者所有按钮都消失了,不要惊慌,只需等待,十五到二十秒后,将显示必要的交易品种。它仍然比手动查看数百个交易品种的图表快。另一方面,在MetaTrader 4中,您需要独立地提供必要交易品种和时间框架的历史记录,这也需要时间和注意这些琐事。
此外,如果您的计算机有少量的RAM(或者您正在使用的虚拟机RAM数量有限,如1 GB),那么在MQL5中,打开自动挑选选项卡时,EA操作可能会因内存不足错误而中断。由于整个时间段历史记录都是在不同的深度独立上载的,因此MQL4没有这样的问题。在MQL5中,可以通过限制 "Max bars in chart(图表中的最大柱数)" 参数来解决这个问题。
向自动挑选页面增加功能、
首先,让我们定义如何将自动挑选选项卡添加到实用程序中。为此,我们需要从内部了解这些选项卡的机制是如何设计的。
我们已经在前面的文章中实现了增加选项卡的功能,您也许记得,我们是在循环中做的。所增加的选项卡的名称是保存在数组中的,自动挑选选项卡的名称也是在独立的数组中保存的:
string panelNamesAddon[9]={"Air Level", "Parabolic", "Gap", "4 weeks Min/Max", "365 days Min/Max", "Round levels", "Mostly Up/Down", "All time high/low", "High=Close"};
换句话说,为了添加自定义选项卡,我们需要将数组大小增加1,并在选项卡列表的末尾添加选项卡名称。之后,新选项卡将出现在实用程序中。
show_panel_buttons 函数就是负责显示选项卡的。它的代码已经修改过了,所有自动挑选选项卡都显示在原有选项卡之后:
void show_panel_buttons(){ int btn_left=0; // 定义可以显示选项卡的最大 x 坐标。 int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77; string tmpName=""; for( int i=0; i<ArraySize(panelNames); i++ ){ // 如果新按钮的开始坐标超过了最大可能坐标, // 转到新的一行 if( btn_left>btn_right-BTN_WIDTH ){ btn_line++; btn_left=0; } // 如果 "homework" 选项卡含有交易品种,就把它们的编号 // 加到选项卡名称中 tmpName=panelNames[i]; if(i>0 && arrPanels[i].Total()>0 ){ tmpName+=" ("+(string) arrPanels[i].Total()+")"; } // 显示选项卡按钮 ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_BGCOLOR,clrSilver); ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName); ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false); // 如果按钮选项卡当前激活, // 把它设为按下状态 if( cur_panel == i ){ ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true); } btn_left+=BTN_WIDTH; } // 如果输入参数中允许显示自动挑选选项卡,就显示它们 if(useAddonsLevels){ for( int i=0; i<ArraySize(panelNamesAddon); i++ ){ if( btn_left>btn_right-BTN_WIDTH ){ btn_line++; btn_left=0; } tmpName=panelNamesAddon[i]; // 如果选项卡称为 Gap, 就显示当前输入参数的值 // 定义跳空百分比 if(tmpName=="Gap"){ StringAdd(tmpName, " ("+(string) gap_min+"%)"); } ObjectCreate(0, exprefix+"panels"+(string) (i+ArraySize(panelNames)), OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_BGCOLOR,clrLightSteelBlue); ObjectSetString(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_TEXT,tmpName); ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_SELECTABLE,false); if( cur_panel == i+ArraySize(panelNames) ){ ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_STATE, true); } btn_left+=BTN_WIDTH; } } // 如果设置了注释,显示: if(StringLen(cmt)>0){ string tmpCMT=cmt; if(from_txt){ StringAdd(tmpCMT, ", from txt"); } ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0); ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack); ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT); ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false); } }
我们可以在函数代码中看到,只有在输入参数中通过 addons_infowatch 许可,才显示自动挑选选项卡。另外,我们还加入了另外两个参数来配置自动挑选选项卡:
sinput string delimeter_05=""; // --- 另外的选项卡 --- input bool useAddonsLevels=true; // 显示额外的选项卡 input bool addons_infowatch=true;// 如果不在市场报价中则隐藏该交易品种 input int addon_tabs_scale=3; // 额外选项卡的序号 (0-5)
我相信,这里只需要说明 addons_infowatch 参数,如果设置了这个参数,只有显示于 市场报价 中的交易品种选项卡才会被挑选,否则,就对您经纪商提供的所有交易品种进行挑选。
这样,当运行工具的新版本时,您可以看到新的自动挑选选项卡:
但是让我们回到我们的代码中,现在,我们知道如何添加自动挑选选项卡了,但是,由于交易品种显示尚未实现,因此单击它们时,目前尚未显示任何交易品种。所有当前打开选项卡的按钮都是使用 show_symbols 函数来显示的,我们将在这里加上自动挑选选项卡的显示,结果,函数看起来如下:
void show_symbols(){ // 初始化用于定义 X 和 Y 坐标的变量 int btn_left=0; int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77; btn_line++; if( cur_panel==0 ){ ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false); ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear All"); btn_left+=BTN_WIDTH; ObjectCreate(0, exprefix+"showpos", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_BGCOLOR,clrPaleTurquoise); ObjectSetInteger(0,exprefix+"showpos",OBJPROP_SELECTABLE,false); ObjectSetString(0,exprefix+"showpos",OBJPROP_TEXT,"Show Pos"); btn_left+=BTN_WIDTH; }else if( cur_panel<ArraySize(arrPanels) && arrPanels[cur_panel].Total()>0 ){ ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false); ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear"); btn_left+=BTN_WIDTH; ObjectCreate(0, exprefix+"new_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_SELECTABLE,false); ObjectSetString(0,exprefix+"new_"+(string) cur_panel,OBJPROP_TEXT,"Open All"); btn_left+=BTN_WIDTH; } // 如果当前打开的选项卡索引超过了 homework 选项卡数组元素的元素数 // 加上所有交易品种的选项卡 // 则这就是自动挑选选项卡 if(cur_panel>(ArraySize(arrPanels)-1)){ MqlRates rates[]; ArraySetAsSeries(rates, true); int tmpNumAddon=cur_panel-ArraySize(arrPanels); addonArr.Resize(0); arrTT.Resize(0); string addonName; CArrayString tmpSymbols; if( tmpNumAddon==0 && air_only_home ){ for( int j=1; j<ArraySize(arrPanels); j++ ){ for( int k=0; k<arrPanels[j].Total(); k++ ){ string curName=arrPanels[j].At(k); bool isYes=false; for( int r=0; r<tmpSymbols.Total(); r++ ){ if(tmpSymbols.At(r)==curName){ isYes=true; break; } } if(!isYes){ tmpSymbols.Add(arrPanels[j].At(k)); } } } }else{ if( ArraySize(result)>1 ){ for(int j=0;j<ArraySize(result);j++){ StringReplace(result[j], " ", ""); if(StringLen(result[j])<1){ continue; } tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix); } }else{ for( int i=0; i<SymbolsTotal(addons_infowatch); i++ ){ tmpSymbols.Add(SymbolName(i, addons_infowatch)); } } } switch(tmpNumAddon){ case 0: // 水平 // 选项卡内容显示代码 break; case 1: // 抛物线 // 选项卡内容显示代码 break; case 2: // 跳空 // 选项卡内容显示代码 break; case 3: //4周最小值/最大值 // 选项卡内容显示代码 break; case 4: //365 天最小值/最大值 // 选项卡内容显示代码 break; case 5: // 近似水平 // 选项卡内容显示代码 break; case 6: // 最上升/下降 // 选项卡内容显示代码 break; case 7: // 所有时间最高价/最低价 // 选项卡内容显示代码 break; case 8: // 最高价=收盘价 // 选项卡内容显示代码 break; } // 数组中的每个交易品种都在图表中显示一个按钮 // 按钮上加上交易品种名称 for( int i=0; i<addonArr.Total(); i++ ){ if( btn_left>btn_right-BTN_WIDTH ){ btn_line++; btn_left=0; } ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,addonArr.At(i)); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false); if( arrTT.At(i)>0 ){ ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TOOLTIP,(string) arrTT.At(i)); } if( checkSYMBwithPOS(addonArr.At(i)) ){ ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff); } btn_left+=BTN_WIDTH; } // 因为按钮已经显示,退出函数 return; } // 在图表上显示数组中每个交易品种的按钮 // 当前活动选项卡 // 按钮上加上交易品种名称 for( int i=0; i<arrPanels[cur_panel].Total(); i++ ){ if( btn_left>btn_right-BTN_WIDTH ){ btn_line++; btn_left=0; } ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanels[cur_panel].At(i)); ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false); if( !noSYMBwithPOS || cur_panel>0 ){ if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){ ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff); } } btn_left+=BTN_WIDTH; } }
我们可以看到,显示选项卡内容的代码位于 switch 操作符之内,数组元素的索引包含在选项卡名称中,在 case 操作符中作为指示,这样,为了加上一个自定义选项卡,在把它的名称加到数组中之后,还要价上新的 case ,使用超过最后使用索引超过1的数值。
在考虑特定选项卡时,我们将更多地了解用于按特定参数自动挑选交易品种的代码示例。但是我们已经看到了所有标签的代码都是以类似的方式开始的。
需要挑选的所有交易品种列表已经存在于 tmpSymbols 数组中,所以,每个选项卡的代码都从 for 循环开始:
for( int i=0; i<tmpSymbols.Total(); i++ ){ addonName=tmpSymbols[i]; // 如果交易品种应当显示,定义的代码 }
空中水平 (盘整范围)
当从水平进行交易时,所有的交易都是在平的范围内进行的,即当价格在每一个水平上以其高或低触及相同或近似相同的价格时。示例显示在下图中:
着也许不是个很好的例子,因为取得的水平经常遇到假突破。但是另一方面,人们相信,假突破的存在会加强这一水平。=)
搜索这样的情况是一项非常繁琐的任务,所以让我们尝试将其自动化。
对平盘范围的搜索通常在M5图表上执行,尽管这不是强制条件,有些交易者在M15或者M30图表上进行。人们相信,在一个平面范围内检测到的时间越长,潜在的进入越好。
所以,让我们加上输入参数来使我们定义所需的时间框架:
sinput string delimeter_06=""; // --- 额外的空中水平选项卡 --- input bool air_level_m5=true; // 在 M5 上寻找空中水平 input bool air_level_m15=true; // 在 M15 上寻找空中水平 input bool air_level_m30=false; // 在 M30 上寻找空中水平 input bool air_level_h1=false; // 在 H1 上寻找空中水平 input bool air_level_h4=false; // 在 H4 上寻找空中水平 input bool air_level_d1=false; // 在 D1 上寻找空中水平
应该记住,选择的时间越长,搜索过程就越慢。因此,最好将搜索限制在一个或两个时间框架内。
另外,让我们加上一些其它的输入参数:
input uchar air_level_count=4; // Number of level bars(水平的柱数) input uchar air_level_offset=3; // Offset relative to level in points(相对水平的点数偏移) input bool air_level_cur_day=true; // Only levels in the current day's direction(只有当天方向的水平) input bool air_level_prev_day=true; // Only levels in the previous day's direction(只有前一天方向上的水平) input bool air_only_home=false; // Search only in "homework"(只在 "homework" 中搜索)
Number of level bars 参数让我们指定触及最近价格的柱数以假定这是个平盘的范围,这个参数的最佳值是4个柱。柱数越大,平盘范围的质量就越高,但是找到它们的频率就越低。
Offset relative to level in points 参数让我们指定范围的点数,也就是价格被认为“位置很近”,换句话说,如果参数为0,柱应当正好触及相同的价格,一般来说,这种情况很少,特别是在较高的时间框架上。在大多数情况下,价格无法达到指定的水平,因此,默认参数值为3点。
如果您只在当前和/或前一天的方向上交易, Only levels in the current day's direction 和 Only levels in the previous day's direction 可以挑选交易品种,它们的平盘区域只在价格变化的相反方向。
价格运动方向是由收盘价与开盘价的比率来定义的。如果前一天柱的收盘价超过了开盘价,则只搜索做多方向的平盘,即那些触及附近价格的“低价”。
最后,让我们看以下最后的参数 – Search only in "homework". 如果在一个交易日中,您只在加到 homework 选项卡中的交易品种中交易,就可以设置这个参数,只在当前加到 homework 选项卡中的交易品种中搜索平盘区域。
现在让我们最后看看选择当前含有平盘区域交易品种的代码,在M5时间框架中的代码提供如下:
if(air_level_m5 && CopyRates(addonName, PERIOD_M5, 0, air_level_count+1, rates)==air_level_count+1){ if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)<=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)<=0) && rates[0].high<rates[1].high ){ bool isOk=true; for( int j=1; j<air_level_count; j++ ){ if( MathAbs(rates[1].high-rates[j+1].high) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){ }else{ isOk=false; } } if(isOk && !skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(rates[1].high); } }else if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)>=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)>=0) && rates[0].low>rates[1].low ){ bool isOk=true; for( int j=1; j<air_level_count; j++ ){ if( MathAbs(rates[1].low-rates[j+1].low) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){ }else{ isOk=false; } } if(isOk && !skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(rates[1].low); } } }
与所有其它自动挑选选项卡的代码示例类似,这里我们从执行 CopyRates 函数调用开始,在执行之后立即调用结果,这并不完全是正确的。MQL 帮助文档建议等待一段时间,以使工具接收并把数据写到数组中。但是就算我们等待50毫秒,检查100个交易品种也就意味着要延迟5秒钟,而很多经纪商提供几百个股票市场交易品种。这样的话,在使用延迟时,显示选项卡的内容可能会花费更多的时间,
所以我们立即开始操作结果,在实践中,这是没有问题的,除了可能是由没有延误引起的问题。
事实上,实际数据并不总是由于CopyRates函数而传输到数组。有的时候选择使用的是老的数据,在这种情况下,只要更新选项卡(R键)来取得相关的交易品种列表就可以了。
让我们回过来看代码。如果决定添加自定义自动排序选项卡,请注意如何进行交易品种的选择。
如果交易品种满足我们的条件,我们就把它的名称放到 addonArr 数组,此外,我们可以指定打开交易品种图表时要使用的时间范围,而不是括号中的默认时间范围。
我们还要把数值输入到 arrTT 数组,如果我们把数组中的值设为零,什么都不会发生。但是,如果我们添加一些价格,则在打开相应符号的图表时,水平线将以指定的价格水平构建。这样做是为了方便起见,这样您就可以立即看到平坦范围检测到的价格。
抛物线弧形
当价格开始向相反的方向移动,并且每个新的柱的最高价或最低价都高于或低于前一个柱时,抛物线弧形出现在一个方向移动之后。 人们认为,在这种情况下,价格更有可能朝着涨跌高的方向发展。在这种情况下,可能使用小的止损。
换句话说,价格下降之后,价格开始上涨,每一个柱的低点都高于前一个柱。在这种情况下,进入多头交易,而把止损放到至少前一个柱的低点。
可以把下面的图表作为例子:
抛物线弧形的示例使用箭头标记,
我们将在M5上使用下面的代码搜索抛物线弧形:
if(CopyRates(addonName, PERIOD_M5, 0, 6, rates)==6){ if( rates[0].low>rates[1].low && rates[1].low>rates[2].low && rates[2].low<=rates[3].low && rates[3].low<=rates[4].low && rates[4].low<=rates[5].low ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } }else if( rates[0].high<rates[1].high && rates[1].high<rates[2].high && rates[2].high>=rates[3].high && rates[3].high>=rates[4].high && rates[4].high>=rates[5].high ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } } }
换句话说,如果当前柱的最低价超过前一个柱的最低价,而那个柱的最低价也超过它前面一个柱的最低价,而剩下的三个前面的柱或者它们的最低价相同或者每个都更小一些。换句话说,如果三个柱都下跌而后面是两个上升的柱,就认为这是多头抛物线弧形的开端。
缺口
如果您使用的交易策略适用于在一个方向或另一个方向存在跳空的股票,则Gap(跳空)选项卡将帮助您选择必要的股票。它显示当前一天中具有跳空的交易品种。您可以修改最小跳空 (以当前价格的百分比), 使用的是 Minimum gap size 参数。
默认情况下,它只显示含有至少 1% 跳空的交易品种。.
选项卡的源代码很简单:
if(CopyRates(addonName, PERIOD_D1, 0, 2, rates)==2){ if( rates[0].open>rates[1].close+(rates[0].open*(gap_min/100)) || rates[0].open<rates[1].close-(rates[0].open*(gap_min/100)) ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } } }
4周最高/最低
4 周最高/最低 选项卡所提供的交易品种列表是,它们的当前价格在四周内的最高点/最低点。它的代码如下:
if(CopyRates(addonName, PERIOD_W1, 0, 4, rates)==4){ bool newMin=true; bool newMax=true; if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){ newMin=false; newMax=false; }else{ for( int j=1; j<4; j++ ){ if( rates[0].high < rates[j].high ){ newMax=false; } if( rates[0].low > rates[j].low ){ newMin=false; } } } if( newMin || newMax ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } } }
年度最高/最低
与前一个选项卡类似,当前的选项卡显示的是交易品种价格位于一年中的最高/最低点。
if(CopyRates(addonName, PERIOD_W1, 0, 52, rates)==52){ bool newMin=true; bool newMax=true; if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){ newMin=false; newMax=false; }else{ for( int j=1; j<52; j++ ){ if( rates[0].high < rates[j].high ){ newMax=false; } if( rates[0].low > rates[j].low ){ newMin=false; } } } if( newMin || newMax ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } } }
价格接近取整水平
人们认为,股票的取整价格是“自然”的支撑/阻力水平。因此,一些交易系统将重点放在当前在其取整水平上交易的工具上。
取整价格是那种以0或者50分结尾的价格,例如125美元0分或者79元50分。
结果,我们将有下面的代码:
switch((int) SymbolInfoInteger(addonName, SYMBOL_DIGITS)){ case 0: break; case 2: if(CopyRates(addonName, PERIOD_M5, 0, 1, rates)==1){ double tmpRound=rates[0].close - (int) rates[0].close; if( (tmpRound>0.46 && tmpRound<0.54) || tmpRound>0.96 || tmpRound<0.04 ){ if(!skip_symbol(addonName)){ addonArr.Add(addonName+" (M5)"); arrTT.Add(0); } } } break; }换言之,我们将只为具有两位小数的符号定义取整价格。如果您使用其他工具,只需以类似的方式为它们添加您自己的检验。
多数时间上涨/下跌
大部分时间上涨或者下跌的资产工具也可能特别有趣。让我们加上以下输入参数来找到它们:
sinput string delimeter_08=""; // --- 额外的多数上涨/下跌选项卡 --- input int mostly_count=15; // Check the last specified number of days(检查最近指定的天数) input int mostly_percent=90; // Specified percentage in one direction exceeded(指定在一个方向上超过的百分比)
Check the last specified number of days 参数可以定义寻找所需资产工具的天数,换句话说,我们大多数情况会在D1中寻找一个方向上的变化。
Specified percentage in one direction exceeded 参数可以指定最小百分比,就是相互超过的百分比。默认值是 90,这表示,为了达到这个选项卡,在指定期间内的价格变化应该在90%以上的天数在某个方向上变化。
一般来说,这样的交易品种数量较少。所以,也许有必要减少这个百分比。
选项卡代码如下:
if(CopyRates(addonName, PERIOD_D1, 1, mostly_count, rates)==mostly_count){ int mostlyLong=0; int mostlyShort=0; for( int j=0; j<mostly_count; j++ ){ if(rates[j].close>rates[j].open){ mostlyLong++; }else if(rates[j].close<rates[j].open){ mostlyShort++; } } if( !mostlyLong || !mostlyShort ){ addonArr.Add(addonName); arrTT.Add(0); }else if( ((mostlyLong*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){ addonArr.Add(addonName); arrTT.Add(0); }else if( ((mostlyShort*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){ addonArr.Add(addonName); arrTT.Add(0); } }
每个柱有新高/低
这个选项卡可以侦测交易品种中的积累过程,也就是说价格在一个方向上的变化缓慢但是持续。一般来说,持续积累会导致在它的方向上最终突破(大柱)。
以下输入参数有助于我们侦测单个方向的变化:
sinput string delimeter_09=""; // --- 额外的 总有新高/低选项卡 --- input ENUM_TIMEFRAMES alltime_period=PERIOD_D1; // 时段 input int alltime_count=15; // Check the last specified number of bars(检查最近指定的柱数)
选择代码如下:
if(CopyRates(addonName, alltime_period, 1, alltime_count, rates)==alltime_count){ bool alltimeHigh=true; bool alltimeLow=true; for( int j=1; j<alltime_count; j++ ){ if(rates[j].high>rates[j-1].high){ alltimeHigh=false; } if(rates[j].low<rates[j-1].low){ alltimeLow=false; } } if( alltimeHigh || alltimeLow ){ addonArr.Add(addonName); arrTT.Add(0); } }
交易日在当日高点/低点结束
这是我们要添加的最后一个选项卡。根据交易时间的不同,它允许您检测:
- before a session starts – 前一天收盘时处于高点/低点的交易品种;
- after a session starts – 当前处于当日高点/低点的交易品种。
据信,如果价格在当天的高点/低点收盘,那么买方/卖方还没有时间执行他们的计划。这就是说,第二天价格会朝着同一个方向走。
以下参数将帮助我们搜索合适的交易品种:
sinput string delimeter_10=""; // --- 额外的 最高价=收盘价 选项卡 --- input ENUM_TIMEFRAMES highclose_period=PERIOD_D1; // 时段 input int highclose_offset=0; // 最高价/最低价偏移点数
在寻找一天的高点/低点时,价格可能不一定直接接近一个极端点,从边界价格上退几个甚至十几个点也是可能的。Error margin of highs/lows in points 参数允许定义允许的回滚点。
选项卡代码如下:
if(CopyRates(addonName, highclose_period, 0, 1, rates)==1){ if( rates[0].close+highclose_offset >= rates[0].high ){ addonArr.Add(addonName); arrTT.Add(0); }else if( rates[0].close-highclose_offset <= rates[0].low ){ addonArr.Add(addonName); arrTT.Add(0); } }
添加自定义选项卡
当然,我们还没有探讨完所有可能的模式,如果您使用其他模式并具有MQL中的编程技能,则可以轻松地将自己的选项卡添加到实用程序中。欢迎您发布您自己选项卡的代码,并在注释中说明他们搜索的模式。
对上述规范的改进建议也非常感谢。
最后,让我提醒您如何将自己的自动挑选选项卡添加到实用程序中。这可以分两步完成,
第一步,把新选项卡的名称加到 panelNamesAddon 选项卡名称数组,不要忘记把数组大小增加1。
第二步, show_symbols 函数的 switch 操作符要加上新的 case 以及使用最后的数字加1. 检查当前交易品种是否符合条件的代码实现在 case 操作符内部,代码模板如下:
case index: // 选项卡名称 for( int i=0; i<tmpSymbols.Total(); i++ ){ addonName=tmpSymbols[i]; // 检查代码 } break;
结论
我们进一步扩展了实用程序的功能。我相信,它对交易者更有用。
在本文中,我们没有根据 MQL 语言版本重写代码,因为它在 MQL4 和 MQL5 中都有效。
如您所见,在MQL中开发跨平台实用程序并不是一个很大的挑战,MQL4以类似的方式支持大多数MQL5功能。因此,可能值得暂时忘记MQL5的各种类和其他独特功能,并让尽可能多的交易者可以使用您的工作。
当然,我不是向类宣战,类已经存在于 MQL4 中并且在代码库中已经出现了,我只建议将 MQL5 语言的这个特性推迟一段时间。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/5517
注意: 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.




谢谢,这项工作很有意思,我会好好研究一下。
那您为什么使用标准 MT 库中的类?
如果您仍然使用标准 MT 类库,为什么不使用类来创建图形对象?- 代码将变得更易读、更简短....2177 行代码
恕我直言,这篇文章无论从实用角度还是理论角度来看都值得商榷。