股票量化交易软件:跳空缺口是能够获利的策略还是五五开?

在这里赫兹股票量化将会在股票市场上检验 D1 上的跳空缺口,市场继续按照缺口方向变化会有多么频繁呢?市场在出现跳空缺口后会反转吗?我将会在这篇文章中尝试回答这些问题, 同时会使用自定义的 CGraphic 图表来显示结果。交易品种的文件是使用系统的 GetOpenFileName DLL 函数来选择的。

选择哪一个市场?
我只对D1时段中的跳空缺口有兴趣,
很明显,与商品交易品种相比, 股票中发现的跳空缺口要多得多, 因为股票是从早上到晚上交易的, 而不是连续的。我对股票特别有兴趣,因为它们有相对长的历史,而期货从另一方面看就不是非常适合,因为它们的生命周期经常只有三或六个月,这对于在D1时段上对历史做研究就不够。
在文档"数据访问处理"部分中的 TestLoadHistory.mq5 脚本可以定义当前交易品种的数量和服务其上存在的D1时段的柱数。下面是一个用于检查 ABBV 交易品种D1时段柱数的例子:

编辑
图 1. ABBV 交易品种
过程如下:
首先,保存在文档中所描述的脚本,为此,要在 MetaEditor 5 ("创建一个脚本") 中创建一个新的脚本,让赫兹股票量化把它命名为 TestLoadHistory.mq5。现在,赫兹股票量化需要从文档中复制脚本文本,然后粘贴到 TestLoadHistory.mq5 脚本中 (所粘贴的文字应当替换掉脚本中的全部文字)。
编译得到的脚本 (在编译之后,脚本在终端的导航器窗口中就可以看到了),
在 MetaTrader 5 中运行脚本。因为检查是为 ABBV 交易品种进行的, 我们需要准备图表: 打开 ABBV 交易品种的图表并把时段设为 D1,从导航器窗口选择脚本并且在 ABBV 图表上运行它,在脚本参数中,把交易品种名称设为 ABBV,选择 D1 时段并把1970年设为日期:

编辑切换为居中
图 2. 运行 TestLoadHistory.mq5 脚本
脚本运行结果:
TestLoadHistory (ABBV,D1) Start loadABBV,Dailyfrom1970.03.16 00:00:00 TestLoadHistory (ABBV,D1) Loaded OK TestLoadHistory (ABBV,D1) First date 2015.09.18 00:00:00 - 758 bars
— 历史从 2015 年开始并且含有 758 个 D1 柱。这个数字对于分析是足够了。
操作一组交易品种
为了分析和计算任何标准,赫兹股票量化都需要比较一个交易品种组中的交易品种。按照规则,MetaTrader 5 终端中的交易品种已经分组 (在市场报价窗口中用鼠标右键点击和选择交易品种,或者按下 Ctrl + U):

编辑
图 3. NASDAQ (SnP100) 组的交易品种
在图中NASDAQ(SnP100)组就被选中了,这个组中就包括 ABBV 交易品种。操作一组交易品种的最方便的方法就是确保脚本是从这个组的交易品种中运行的,为了在每个组中做迭代,赫兹股票量化需要从每个组中手动打开一个交易品种图表并运行 Symbols on symbol tree.mq5 工具脚本 — 这个脚本会把所有组里的交易品种 (交易品种名称) 收集到一个单独的文件中。
Symbols on symbol tree.mq5 脚本是根据下面的算法来工作的: 在 SYMBOL_PATH 交易品种树中取得路径;从取得的路径中读取最终的交易品种组 (这里就是 NASDAQ(SnP100) 组); 从这个组中选择所有的交易品种并把选中的交易品种保存到一个文件中。在交易品种树中,路径中的文件名称里面,所有的 "/" 和 "\" 字符都使用 "_" 代替(代替过程是在脚本中自动进行的,文件名称也是自动生成的)。在替换了字符之后,为 NASDAQ(SnP100) 交易品种组生成的名称如下: "Stock Markets_USA_NYSE_NASDAQ(SnP100)_.txt".
为什么赫兹股票量化需要把每个组放到单独的文件中呢?因为这样我们就能够从组文件中直接简单读取交易品种名称了,而不用迭代所有的交易品种再进行缺口的分析。也就是说,Symbols on symbol tree.mq5 工具脚本可以去掉从指定的交易品种组中选择交易品种的过程。
Symbols on symbol tree.mq5 script
让我们研究一下脚本的运行。
请注意: 只有在专家页面中出现 "Everything is fine. There are no errors" 的文字信息才能保证脚本的工作已经成功,并且所得到的文件可以用于将来的工作!
为了缩短文件操作的代码, 包含了 CFileTxt 类, 而对文本文件的操作是由 m_file_txt — CFileTxt 类对象来完成的。脚本的工作可以分成七步:
//+------------------------------------------------------------------+ //| Symbols on symbol tree.mq5 | //| Copyright © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" //--- #include <Files\FileTxt.mqh> CFileTxt m_file_txt; // 文本文件对象 //--- string m_file_name=""; // 文件名 //+------------------------------------------------------------------+ //| 脚本程序起始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- 第一步 string current_path=""; if(!SymbolInfoString(Symbol(),SYMBOL_PATH,current_path)) { Print("ERROR: SYMBOL_PATH"); return; } //--- 第二步 string sep_="\\"; // 分隔符字符 ushort u_sep_; // 用于分隔字符的代码 string result_[]; // 用于取得字符串的数组 //--- 取得分隔符代码 u_sep_=StringGetCharacter(sep_,0); //--- 把字符串分成子字符串 int k_=StringSplit(current_path,u_sep_,result_); //--- 第三步 //--- 现在输出所有取得的字符串 if(k_>0) { current_path=""; for(int i=0;i<k_-1;i++) current_path=current_path+result_[i]+sep_; } //--- 第四步 string symbols_array[]; int symbols_total=SymbolsTotal(false); for(int i=0;i<symbols_total;i++) { string symbol_name=SymbolName(i,false); string symbol_path=""; if(!SymbolInfoString(symbol_name,SYMBOL_PATH,symbol_path)) continue; if(StringFind(symbol_path,current_path,0)==-1) continue; int size=ArraySize(symbols_array); ArrayResize(symbols_array,size+1,10); symbols_array[size]=symbol_name; } //--- 第五步 int size=ArraySize(symbols_array); if(size==0) { PrintFormat("ERROR: On path \"%s\" %d symbols",current_path,size); return; } PrintFormat("On path \"%s\" %d symbols",current_path,size); //--- 第六步 m_file_name=current_path; StringReplace(m_file_name,"\\","_"); StringReplace(m_file_name,"/","_"); if(m_file_txt.Open("5220\\"+m_file_name+".txt",FILE_WRITE|FILE_COMMON)==INVALID_HANDLE) { PrintFormat("ERROR: \"%s\" 文件没有在通用数据文件夹中创建",m_file_name); return; } //--- 第七步 for(int i=0;i<size;i++) m_file_txt.WriteString(symbols_array[i]+"\r\n"); m_file_txt.Close(); Print("Everything is fine. There are no errors"); //--- } //+------------------------------------------------------------------+
脚本运行的算法:
第一步: 为当前交易品种定义 SYMBOL_PATH (交易品种树中的路径);
第二步: 取得的路径使用"\"分隔符分成子字符串;
第三步: 重新组装当前路径,不包括最后的子字符串,因为它包含着交易品种名称;
第四步: 在所有可用交易品种中循环;如果交易品种的路径与当前路径匹配,就选择交易品种名称并把它加到侦测到的交易品种数组中;
第五步: 检查侦测到的交易品种数组的大小;
第六步: 生成文件名 (从名字中删除 "/" 和 "\" 字符,生成文件);
第七步: 把侦测到的交易品种数组写到文件中并关闭它。
注意第六步: 文件是在通用文件目录(使用了FILE_COMMON标志)创建的。
另外,要确保脚本的运行没有错误,应该在专家页面的记录中出现下面的信息: "Everything is fine. There are no errors. Create file:". 文件的名称在下一行显示 — 复制它并把它粘贴到 "Getting gap statistics ..." 脚本中。成功生成的 文件在下面显示:
On path "Stock Markets\USA\NYSE/NASDAQ(SnP100)\" 100 symbols Everything is fine. There are no errors. Create file: Stock Markets_USA_NYSE_NASDAQ(SnP100)_
这样,我们就取得了文件 (这里它是 Stock Markets_USA_NYSE_NASDAQ(SnP100)_) 每行一个交易品种。文件的前五行:
AAPL ABBV ABT ACN AGN
收集数据
根据交易品种的 OHLC 历史数据和统计计算是在主脚本 Getting gap statistics.mq5 中进行的,为每个交易品种都要填充 SGapStatistics 结构: struct SGapStatistics { string name; // 交易品种名称 int d1_total; // D1 柱的总数 int gap_total; // 缺口的总数 int gap_confirmed; // 确认的缺口数量 }; name — 交易品种名称d1_total — 根据交易品种的D1柱的数量gap_total — 侦测到的缺口数量gap_confirmed — 确认过的缺口数量 (例如,一天是以向上的缺口开始,而收盘是上升的柱) 最适合用于为每个交易品种取得 OHLC 价格的函数就是 CopyRates. 赫兹股票量化将会使用它的第三种形式 — 根据所需时间段的起始和结束日期。对于开始时间,我们会使用 TimeTradeServer 交易服务器的当前时间加上一天,而结束日期是1970年1月1日。 现在,赫兹股票量化所要做的就是定义如何处理错误 (请求结果返回了 "-1" ) 或者如何判断是否所有的根据请求的数据都已返回 (例如,并不是所有数据都已经从服务器上下载了)。我们可以用简单的方法来做 (请求 — 暂停 N — 秒 — 新的请求),或者使用正确的方法。正确的方案是基于从文档的"数据访问处理" 部分中的TestLoadHistory.mq5脚本改进而得到的。