概述
在之前有关交易中应用货币对篮的文章中, 我们研究了交易原则, 技术分析手段以及可以通过这些手段检测的形态。当然, 若无某些形态参数的确认, 盲目追随这些方法是不可行的。例如, 我们需要澄清超卖/超买等级的具体价位。在此, 我们将查证检测到的形态参数, 并尝试为交易者制定建议。
研究工具
为了我们的工作, 我们将使用之前开发的 "组合 WPR"。我们经常在之前的文章系列中应用它, 且已证明它有助于检测大多数的形态。
将 WPR 周期从 14 增加到 20, 以便略微平滑指标图表。这可令您 "缩小" 图表, 而不会损失显示品质。
我们将研究三个时间帧: D1, H4 和 H1。您可以使用此处所描述的方法获取其它周期的结果。
基本的术语和原则可以 在此 找到。
研究形态
我们开始研究 在此处 描述的形态 #3。形态十分简单。它等同于一个单独的、众所周知的长线货币对。交易货币对时应用如下方式:
当蜡烛收盘时, 若组合 WPR 下穿超买等级, 或是上穿超买等级, 交易者将收到篮内所有货币对的入场信号。
我们在哪里可以找到这些超卖/超买等级?若在单独的货币对上使用标准 WPR, 我们可以轻松地回答这个问题:
- 超买等级: - 20%
- 超卖等级: - 80%
这为我们的研究奠定了起点。我们将使用这些数据来澄清组合 WPR 等级的位置。结果将不仅有助于检查问题中的形态, 而且也有助于其它类似的情况。应用的方法同样也很方便。
指标线应高于超买等级或低于超卖等级, 突破其中之一即可。我们来分析历史数据, 定义潜在的入场数量。我们现阶段不打算使用指标。代之, 我们将应用以前开发的 testIndexZig-Zag1.mq5 和 testWPReur.mq5 指标。在 testWPReur.mq5 当中, 我们简单地根据篮子的组件替换数据。我们稍微简化 testIndexZig-Zag1.mq5 指标源代码, 因为我们已经知道指标的最高点和最低点 (从 100% 到 -100%):
#property copyright "版权所有 2016, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 6 #property indicator_plots 3 //--- 绘制最高点 #property indicator_label1 "High" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- 绘制最低点 #property indicator_label2 "Low" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGreen #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- 绘制之字折线 #property indicator_label3 "ZigZag" #property indicator_type3 DRAW_SECTION #property indicator_color3 clrRed #property indicator_style3 STYLE_SOLID #property indicator_width3 1 //--- 绘制方向 #property indicator_label4 "Direction" #property indicator_type4 DRAW_LINE #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- 绘制最后的最高柱线 #property indicator_label5 "LastHighBar" #property indicator_type5 DRAW_LINE #property indicator_style5 STYLE_SOLID #property indicator_width5 1 //--- 绘制最后的最低柱线 #property indicator_label6 "LastLowBar" #property indicator_type6 DRAW_LINE #property indicator_style6 STYLE_SOLID #property indicator_width6 1 #include <ZigZag\CSorceData.mqh> #include <ZigZag\CZZDirection.mqh> #include <ZigZag\CZZDraw.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum EDirection { Dir_NBars=0, Dir_CCI=1 }; //--- 输入参数 input EDirection DirSelect=Dir_NBars; input int CCIPeriod = 14; input ENUM_APPLIED_PRICE CCIPrice = PRICE_TYPICAL; input int ZZPeriod=14; string name; CZZDirection*dir; CZZDraw*zz; //--- 指标缓存区 double HighBuffer[]; double LowBuffer[]; double ZigZagBuffer[]; double DirectionBuffer[]; double LastHighBarBuffer[]; double LastLowBarBuffer[]; //+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int h; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { switch(DirSelect) { case Dir_NBars: dir=new CNBars(ZZPeriod); break; case Dir_CCI: dir=new CCCIDir(CCIPeriod,CCIPrice); break; } if(!dir.CheckHandle()) { Alert("指标 2 下载错误"); return(INIT_FAILED); } zz=new CSimpleDraw(); //--- 指标缓存区映射 SetIndexBuffer(0,HighBuffer,INDICATOR_DATA); SetIndexBuffer(1,LowBuffer,INDICATOR_DATA); SetIndexBuffer(2,ZigZagBuffer,INDICATOR_DATA); SetIndexBuffer(3,DirectionBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(4,LastHighBarBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(5,LastLowBarBuffer,INDICATOR_CALCULATIONS); name = _Symbol + TimeFrameToShortString(Period()) + ".txt"; h=FileOpen(name,FILE_CSV|FILE_WRITE|FILE_ANSI,','); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(CheckPointer(dir)==POINTER_DYNAMIC) { delete(dir); } if(CheckPointer(zz)==POINTER_DYNAMIC) { delete(zz); } } //+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ int ind=0; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[] ) { int start; if(prev_calculated==0) { start=0; } else { start=prev_calculated-1; } for(int i=start;i<rates_total;i++) { HighBuffer[i]=price[i]; LowBuffer[i]=price[i]; } int rv; rv=dir.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer); if(rv==0)return(0); zz.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer, LastHighBarBuffer, LastLowBarBuffer, ZigZagBuffer); if(ind<= 10) ind++; if(ind == 10) { double mx=100,mn=-100; double lg; lg=mx-mn; lg/=100; double levels[100]; int count[100]; ArrayInitialize(count,0); for(int i=1; i<101; i++) levels[i-1]=NormalizeDouble(lg*i + mn,_Digits); for(int i=0;i<rates_total;i++) { if(ZigZagBuffer[i]==0 || ZigZagBuffer[i]==EMPTY_VALUE) continue; else { for(int j=0; j<100; j++) { if(ZigZagBuffer[i]<levels[j]) { count[j]++; break; } } } } for(int i=0; i<100; i++) { FileWrite(h,i,levels[i],count[i]); } FileClose(h); Print("工作完成: ",name); } return(rates_total); } //+------------------------------------------------------------------+ string TimeFrameToShortString(ENUM_TIMEFRAMES period) { switch (period ) { case PERIOD_M1: return ("M1"); case PERIOD_M2: return ("M2"); case PERIOD_M3: return ("M3"); case PERIOD_M4: return ("M4"); case PERIOD_M5: return ("M5"); case PERIOD_M6: return ("M6"); case PERIOD_M10: return ("M10"); case PERIOD_M12: return ("M12"); case PERIOD_M15: return ("M15"); case PERIOD_M20: return ("M20"); case PERIOD_M30: return ("M30"); case PERIOD_H1: return ("H1"); case PERIOD_H2: return ("H2"); case PERIOD_H3: return ("H3"); case PERIOD_H4: return ("H4"); case PERIOD_H6: return ("H6"); case PERIOD_H8: return ("H8"); case PERIOD_H12: return ("H12"); case PERIOD_D1: return ("D1"); case PERIOD_W1: return ("W1"); case PERIOD_MN1: return ("MN1"); } return (""); }
如早前所述, 该指标的主要代码由 这篇文章 中的尊敬同事 Dmitry Fedoseev 开发并提供给社区。提到的两个指标可以在下面的 test.zip 存档中找到。我们已有了必要的工具, 现在我们来查找必要的数据。
可能的交易数量
组合 WPR 的范围在 -100% 到 +100% 之间, 所以我们现在假设超买等级为 +60%, 而超卖等级为 -60%, 符合标准值。我们来看看指标超出超买/超卖等级的次数。为此, 我们要用到此处 描述的方法:
- 将 testIndexZig-Zag1.mq5 指标应用于组合的 WPR 图表 (testWPReur.mq5)。我们的目标是确定超过 +70% 和 +80%, 或 -70% 和 -80% 的极值数量, 如下图所示。注意问题区域标记为蓝色矩形。现在, 这些极值已包含在计算中, 虽然我们将来会对这些值进行整理:
- 所应用的 testIndexZig-Zag1.mq5 指标将 testWPReur.mq5 指标的范围划分为 1% 的间隔, 并定义了每个间隔内的极值数。结果将被发送到文件。重复计算所有选定的时间帧。之后, 我们修改了 testWPReur.mq5 中的篮内数据, 并继续处理下一个货币篮。
为了更便捷, 将所获得的所有篮子和所选时间帧上的数据按表格排列。关于欧元篮子的表格片段如下所示。我们来澄清表格的行和列内数值的含义:
- 编号— 索引编号。
- 指标 — 指标值的 %。例如, 值为 -96 的行表示组合 WPR 处于 -96% 到 -98%的间隔。
- 欧元 — 每个选定的时间帧内含有极值数的三列。例如, 已经提到的编号为 1 的行, 其中含有历史上组合 WPR 指标落于 96% 到 98% 间隔内的极值数量: D1 - 零个, H4 和 H1 - 每个时间帧一个。
- 历史深度 — 用于计算的历史深度。
- 交易计数 (80%) — 每个时间帧的可能入场总数。例如, H4 上的欧元篮子提供了 83 个可能的入场, 这意味着组合 WPR 指标超过 80% 或低于 -80% 的次数。
- 交易计数 (70%) — 组合 WPR 在 70% 的参数相同。
- 交易总数 (80%) — 所有篮子和时间帧的组合 WPR 值在 80% 处的潜在入场总数。
- Trade total (70%) — 在 70% 处相同。
EUR | ---- | ||||
编号 | 指标 | 时间帧 | ---- | ||
D1 | H4 | H1 | ---- | ||
0 | -98 | 2 | 3 | 4 | ---- |
1 | -96 | 0 | 1 | 1 | ---- |
2 | -94 | 0 | 0 | 1 | ---- |
3 | -92 | 0 | 3 | 3 | ---- |
4 | -90 | 1 | 4 | 5 | ---- |
5 | -88 | 3 | 4 | 10 | ---- |
6 | -86 | 1 | 2 | 7 | ---- |
7 | -84 | 2 | 8 | 7 | ---- |
8 | -82 | 1 | 8 | 21 | ---- |
9 | -80 | 4 | 6 | 22 | ---- |
---- | ---- | ---- | ---- | ---- | ---- |
95 | 92 | 0 | 2 | 6 | ---- |
96 | 94 | 0 | 1 | 4 | ---- |
97 | 96 | 0 | 0 | 3 | ---- |
98 | 98 | 0 | 3 | 0 | ---- |
99 | 100 | 0 | 0 | 0 | ---- |
历史深度 | 2000.11.09 | 2005.04.12 | 2006.01.17 | ---- | |
---- | |||||
交易计数 (80%) | 25 | 83 | 165 | ---- | |
交易计数 (70%) | 45 | 207 | 449 | ---- | |
交易总数 (80%) | 3793 | ||||
交易总数 (70%) | 7885 |
该表可以在随附的 Pair.zip 存档中找到。
最后两个表格行包含搜索值。即使考虑到部分信号已经过整理, 还是有相当多的可能入场数量。所以, 我们现在让超卖/超买等级保留在同一个地方。请记住, 所有已发现 (和已经存在的) 值都是概率性的, 并可以调整。
形态形成
我们来定义我们所需的识别入场形态的形状。
- 如果组合 WPR 指标下穿超买等级 +60%, 交易者卖出一篮子货币对。蜡烛收盘时, 指标值不低于 +50%。下降的指标线应不低于 +70%。这一点的第二个选项是 +80% 以上, 超买等级为 +70%。
- 买入一篮子货币对的情况与所述对称。
上述图像中高亮显示的所有三种形态均满足这些条件。我们收到一个清晰的 "美丽" 形态, 其数值和条件可以转换为算法。
这意味着我们需要一款智能交易系统。
测试形态的智能交易系统
首先, 我们来处理买入/卖出篮子。在 这篇文章 里, 您可以找到交易货币篮子的详细信息, 并研究包含每个篮子实用建议的表格。我们来使用这个表格, 并在 EA 代码中实现相同的原则。
我们再次展示我们正在寻找的形态:
目标形态 |
无形态 |
|
|
假设超卖/超买等级可以在 60-70% 的范围内转移。我们来查验一下依据形态的交易数量, 交易时间, 回撤和潜在的盈利能力。我们现在不需要 EA 的稳定利润。我们的目标是进行第一步, 澄清形态的形状。因此, 我们不会发布标准测试报告, 因为我们对 EA 盈利能力不感兴趣, 而我们所需要的数据不包括在标准报告中。我们将重点放在展示获得的结果。
我们将从美元货币篮子开始分析, 将下一个 EA 放在之前所选时间帧的 EURUSD 上:
//+------------------------------------------------------------------+ //| testBasket.mq5 | //| 版权所有 2017, MetaQuotes 软件公司| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2017, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" //--- 输入参数 #include <Trade\\Trade.mqh> #define LG 7 input int SELLPROFIT = 0; input int SELL1LIMIT = 70; input int SELL2FROM = 60; input int SELL2TO = 50; input int BUYPROFIT = 0; input int BUY1LIMIT = -70; input int BUY2FROM = -60; input int BUY2TO = -50; input int WPR=20; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum BSTATE { BCLOSE = 0, BBUY = 1, BSELL = 2 }; string pair[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY"}; bool bDirect[]={false,false,false,false,true,true,true}; datetime TimeParam[3]; double dWpr[3]; ulong Ticket[LG]; double TradeResult[LG]; double TradeCurrency; double Drw; string sLog; double TradeTotalResult[LG]; double TradeTotalCurrency; int iTradeCount; double mDrw; int h1[LG]; BSTATE bstate; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetValue1(int shift) { double dBuf[1]; double res=0.0; for(int i=0; i<LG; i++) { CopyBuffer(h1[i],0,shift,1,dBuf); if(bDirect[i]==true) res+=dBuf[0]; else res+=-(dBuf[0]+100); }//end for (int i = 0; i < iCount; i++) res=res/LG; return (NormalizeDouble((res + 50) * 2, _Digits) ); } //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int lh; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); for(int i=0; i<LG; i++) { h1[i]=iWPR(pair[i],0,WPR); } bstate=BCLOSE; ArrayInitialize(TradeTotalResult,0); ArrayInitialize(dWpr,EMPTY_VALUE); TradeTotalCurrency=0; iTradeCount=0; mDrw=1000000; lh=INVALID_HANDLE; string lname = _Symbol + "_" + TimeFrameToShortString(Period() ); string t1, t = lname; int i=0; for(;;) { t+=".html"; long lg=FileFindFirst(t,t1); if(lg==INVALID_HANDLE) { lh= FileOpen(t,FILE_WRITE | FILE_TXT | FILE_ANSI); Print("CREATE ",t); break; } FileFindClose(lg); t=lname+"_"+IntegerToString(i++); } FileWriteString(lh,"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"); FileWriteString(lh,"<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n"); FileWriteString(lh,"<head>\r\n"); FileWriteString(lh,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n"); FileWriteString(lh,"<title>"+lname+"</title>\r\n"); FileWriteString(lh,"</head>\r\n<body>\r\n"); FileWriteString(lh,"<H2>"+_Symbol+" "+TimeFrameToShortString(Period())+"</H2>\r\n"); FileWriteString(lh,"<H3>形态参数:</H3>\r\n"); FileWriteString(lh,"<table width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n"); FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>BUY</th>\r\n<th>SELL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n"); t=StringFormat("点 1: %d 点 2 从: %d 至: %d 平仓在: %d",BUY1LIMIT,BUY2FROM,BUY2TO,BUYPROFIT); FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n"); t=StringFormat("点 1: %d 点 2 从: %d 至: %d 平仓在: %d",SELL1LIMIT,SELL2FROM,SELL2TO,SELLPROFIT); FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n"); FileWriteString(lh,"</tr>\r\n</tbody>\r\n</table>\r\n"); FileWriteString(lh,"<H2>"+"测试结果"+"</H2>\r\n"); FileWriteString(lh,"<table border=\"1\" width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n"); FileWriteString(lh,"<thead>\r\n<th>编号</th>\r\n<th>类型</th>\r\n<th>WPR(P1/P2)</th>\r\n<th>时间(开始/结束/长度)</th>\r\n<th>回撤/<br/>盈利</th>\r\n<th>货币对盈利</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PushWpr(double wpr) { dWpr[2] = dWpr[1]; dWpr[1] = dWpr[0]; dWpr[0] = wpr; } //+------------------------------------------------------------------+ //| 智能系统即时报价函数的 | //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void Stat() { double d=0; for(int i=0; i<LG; i++) { PositionSelectByTicket(Ticket[i]); d+=PositionGetDouble(POSITION_PROFIT); } if(d<Drw) Drw=d; if(Drw<mDrw) { mDrw=Drw; TimeParam[2]=TimeCurrent(); } } //+------------------------------------------------------------------+ //| 计时器函数 | //+------------------------------------------------------------------+ void OnTimer() { if(bstate!=BCLOSE) { Stat(); } if(IsNewCandle()) { double res=GetValue1(0); PushWpr(res); if(dWpr[1]!=EMPTY_VALUE) { if(bstate==BBUY && (dWpr[0]>=BUYPROFIT )) { CloseAllPos(); bstate=BCLOSE; } if(bstate==BSELL && (dWpr[0]<=SELLPROFIT )) { CloseAllPos(); bstate=BCLOSE; } if(bstate==BCLOSE && dWpr[0]<=SELL2FROM && dWpr[0]>=SELL2TO && dWpr[1]>=SELL1LIMIT) { EnterSell(0.01); bstate=BSELL; TimeParam[0]=TimeCurrent(); TradeCurrency=0; Drw=1000000; iTradeCount++; sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>卖出</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[1],dWpr[0],TimeToString(TimeCurrent())); return; } if(bstate==BCLOSE && dWpr[0]>=BUY2FROM && dWpr[0]<=BUY2TO && dWpr[1]<=BUY1LIMIT) { EnterBuy(0.01); bstate=BBUY; TimeParam[0]=TimeCurrent(); TradeCurrency=0; Drw=1000000; iTradeCount++; sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>买入</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[1],dWpr[0],TimeToString(TimeCurrent())); return; } }//if (stc.Pick(1) != EMPTY_VALUE) } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CloseAllPos() { CTrade Trade; Trade.LogLevel(LOG_LEVEL_NO); TimeParam[1]=TimeCurrent(); string p="<td>"; for(int i=0; i<LG; i++) { TradeResult[i]=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP); p+=StringFormat("%s = %.2f<br/>",pair[i],TradeResult[i]); TradeCurrency += TradeResult[i]; TradeTotalResult[i] += TradeResult[i]; Trade.PositionClose(Ticket[i]); } p+="</td>\r\n"; TradeTotalCurrency+=TradeCurrency; sLog += StringFormat("%s/<br/>%s</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n",TimeToString(TimeParam[1]), TimeIntervalToStr(TimeParam[0], TimeParam[1]), Drw, TradeCurrency ); sLog += p; FileWriteString(lh,sLog); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnterBuy(double lot) { CTrade Trade; Trade.LogLevel(LOG_LEVEL_NO); for(int i=0; i<LG; i++) { if(bDirect[i]) { //发送买入 Trade.Buy(lot,pair[i]); Ticket[i]=Trade.ResultDeal(); } else { //发送卖出 Trade.Sell(lot,pair[i]); Ticket[i]=Trade.ResultDeal(); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnterSell(double lot) { CTrade Trade; Trade.LogLevel(LOG_LEVEL_NO); for(int i=0; i<LG; i++) { if(bDirect[i]) { //发送卖出 Trade.Sell(lot,pair[i]); Ticket[i]=Trade.ResultDeal(); } else { //发送买入 Trade.Buy(lot,pair[i]); Ticket[i]=Trade.ResultDeal(); } } } //+------------------------------------------------------------------+ //| 智能系统逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- 摧毁计时器 EventKillTimer(); FileWriteString(lh,"</tbody>\r\n</table>\r\n"); FileWriteString(lh,"<H2>总结果</H2>\r\n"); FileWriteString(lh,"<table border=\"1\" width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n"); FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>成交<br/>计数</th>\r\n<th>盈利</th>\r\n<th>最大回撤</th>\r\n<th>货币对盈利</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n"); string p = StringFormat("<tr><td>%d</td>\r\n<td>%.2f</td>\r\n<td>%.2f at<br/>%s</td>\r\n<td>",iTradeCount,TradeTotalCurrency,mDrw,TimeToString(TimeParam[2])); for(int i=0; i<LG; i++) { if(h1[i]!=INVALID_HANDLE) IndicatorRelease(h1[i]); p+=StringFormat("%s = %.2f<br/>",pair[i],TradeTotalResult[i]); } p+="</td>\r\n</tr>\r\n"; FileWriteString(lh,p); FileWriteString(lh,"</tbody>\r\n</table>\r\n"); FileWrite(lh,"</body>\r\n</html>"); //结束日志 FileClose(lh); } //+------------------------------------------------------------------+ bool IsNewCandle() { static int candle=-1; int t1=0; switch(_Period) { case PERIOD_H1: t1 = Hour(); break; case PERIOD_H4: t1 = Hour4(); break; case PERIOD_D1: t1 = Day(); break; } if(t1!=candle) {candle=t1; return(true);} return (false); } int Hour4(){return((int)Hour()/4);} //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int Day() { MqlDateTime tm; TimeCurrent(tm); return(tm.day); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int Hour() { MqlDateTime tm; TimeCurrent(tm); return(tm.hour); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string TimeIntervalToStr(datetime dt1,datetime dt2) { string tm; if(dt1 >= dt2) tm = TimeToString(dt1 - dt2); else tm = TimeToString(dt2 - dt1,TIME_DATE|TIME_MINUTES|TIME_SECONDS); string ta[],ta1[]; StringSplit(tm,StringGetCharacter(" ",0),ta); StringSplit(ta[0],StringGetCharacter(".",0),ta1); ta1[0] = IntegerToString( StringToInteger(ta1[0]) - 1970); ta1[1] = IntegerToString( StringToInteger(ta1[1]) - 1); ta1[2] = IntegerToString( StringToInteger(ta1[2]) - 1); return (ta1[0] + "." + ta1[1] + "." + ta1[2] + " " + ta[1]); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string TimeFrameToShortString(ENUM_TIMEFRAMES period) { switch(period) { case PERIOD_H1: return ("H1"); case PERIOD_H4: return ("H4"); case PERIOD_D1: return ("D1"); } return (""); } //+------------------------------------------------------------------+
进行测试的第一个 EA 版本可以在附件的 testBasket.mq5 文件中找到。算法未包含任何特殊的内容, 尽管对报表非常重视。我们来澄清 EA 输入的意思:
- SELLPROFIT。当组合 WPR 指标达到该值时, 所有卖出篮子的持仓平仓。省缺值为 0%。
- SELL1LIMIT。这是点 1 处组合 WPR (见上图) 开始识别篮子卖出形态的最小值。省缺值为 70%。
- SELL2FROM。点 2 处组合 WPR 识别篮子卖出形态的最大值。省缺, 60% 为超买等级。
- SELL2TO。点 2 处组合 WPR 最终识别篮子卖出形态的最小值。省缺是 50%。
- BUYPROFIT。当组合 WPR 指标达到该值时, 所有购买入篮子的持仓平仓。省缺为 0%。
- BUY1LIMIT。这是点 1 处组合 WPR 识别篮子买入形态的最大值。省缺为 -70%。
- BUY2FROM。点 2 处组合 WPR 识别篮子买入形态的最小值。省缺, 60% 是超卖等级。
- BUY2TO。点 2 处组合 WPR 最终识别篮子买入形态的最大值。省缺是 -50%。
- WPR。标准技术指标 WPR 的周期。省缺是 20。
接下来, 从 2016 年 1 月开始在测试器里测试 EA。所选测试日期取决于历史数据质量。我们将分析两种形态的形式。第一种已在上面描述过了, 且按省缺设置。第二种相对于第一种按以下方式平移:
- SELLPROFIT。当组合 WPR 指标达到该值时, 所有卖出篮子的持仓平仓。此值为 0%。
- SELL1LIMIT。这是点 1 处组合 WPR (见上图) 开始识别篮子卖出形态的最小值。此值为 80%。
- SELL2FROM。点 2 处组合 WPR 识别篮子卖出形态的最大值。此值为 70%, 超买等级。
- SELL2TO。点 2 处组合 WPR 最终识别篮子卖出形态的最小值。此值为 50%。
- BUYPROFIT。当组合 WPR 指标达到该值时, 所有购买入篮子的持仓平仓。此值为 0%。
- BUY1LIMIT。这是点 1 处组合 WPR 识别篮子买入形态的最大值。此值为 -80%。
- BUY2FROM。点 2 处组合 WPR 识别篮子买入形态的最小值。此值为 -70%, 超卖等级。
- BUY2TO。点 2 处组合 WPR 最终识别篮子买入形态的最大值。此值为 -50%。
结果为 html 报告。
EA 报表
EA 报告很容易理解。我们以欧元篮子报告为例, 来看一下报告的结构。
第一行包含图表名称标题和 EA 启动时所挂载的时间帧。
其次是 EA 单独用于买入和卖出篮子的形态参数: 点1 — 点 1 位置: SELL1LIMIT (BUY1LIMIT)。点 2 从: ...至: ... — 点 2 位置: SELL2FROM (BUY2FROM) 和 SELL2TO (BUY2TO)。平仓在 — SELLPROFIT (BUYPROFIT) 形态平仓点位置。
EURUSD H4
形态参数:
买入 | 卖出 |
---|---|
|
|
形态参数后面是测试结果表格, 其中包含测试期间每笔交易的数据, 顺序如下:
- 编号— 索引编号
- 类型 — 交易类型: 卖出或买入篮子
- WPR(P1/P2) — 组合 WPR 在点 1 /点 2 处的入场数据
- 时间(开始/结束/长度) — 交易时间数据: 入场时间/离场时间/交易持续时间
- 回撤/盈利 — 每次交易的最大回撤 / 最终利润。以存款货币为单位的数据。
- 货币对盈利 — 组成货币篮子的单个货币对的利润。以存款货币为单位的数据。
从以下表格片段中我们可以看到, 第一笔交易持续了八个小时, 并带来了 16.34 美元的亏损。特别是, EURUSD 的订单以亏损 2.55 美元平仓:
编号 | 类型 | WPR(P1/P2) | 时间(开始/结束/长度) | 回撤/ 盈利 |
货币对盈利 |
---|---|---|---|---|---|
1 | 卖出 | 86.26/ 67.15 |
2016.03.23 20:00/ 2016.03.24 04:00/ 0.0.0 08:00:00 |
-21.70/ -16.34 |
EURUSD = -2.55 GBPUSD = -1.58 AUDUSD = -2.02 NZDUSD = -3.66 USDCAD = -2.15 USDCHF = -2.97 USDJPY = -1.41 |
包含测试周期内汇总数据的 "总结果" 表格顺序如下:
- 成交计数 — 整个测试期间的交易数量。
- 盈利 — 在整个测试期间获得利润。以存款货币为单位的数据。
- 最大回撤 — 最大回撤和检测时刻。数据以存款货币和日期为单位。
- 货币对的盈利 — 组成篮子货币的每个货币对的总利润。以存款货币为单位的数据。
这是直接来自报告的表格:
成交 计数 |
盈利 | 最大回撤 | 货币对的盈利 |
---|---|---|---|
22 | 189.06 | -52.37 于 2016.05.02 19:53 |
EURUSD = 52.43 GBPUSD = 24.50 AUDUSD = 45.88 NZDUSD = 13.94 USDCAD = 15.73 USDCHF = 12.26 USDJPY = 24.32 |
获取的报告附在 DocUSD.zip 中。
值得注意的是, D1 的业务意外地小。不过, 有令人鼓舞的信号:
- EA 在 H4 和 H1 部分显示出积极的结果, 无需来自我们的任何努力。
虽然数据相当有限, 但我们仍然可以作出初步的结论, 今后可以澄清。
- 形态很少在日线时间帧内发现。这一趋势最有可能在高于 D1 的时间帧内继续。
- 超买/超卖等级为, 在超买的情况下 60% — 70%的范围内, 超卖情况下 -60%— -70%。高于 70% 和低于 -70% 没有多少交易。在这种情况下, 点 1 应该在 90% 或 90% 以下才能识别出这种情况, 这是罕见的。低于 60% 或高于 -60%, 点 2 为接近 40% 或 -40%, 接近潜在的横盘区域。该区域的特征在于指标读数波动性更大, 以及更多的假入场信号。
我们来完成 EA 并继续下一个货币篮子 — NZD。首先, 我们应该通过推导 "正回撤" 值来修改报表。这个概念背后的思路是什么?篮子根据指标读数平仓, 而非一定利润值或特定的交易价位。在平仓之前, 订单篮子中的订单可由 EA 监控的回撤。这些相同的订单也许显示出显著利润, 但却无法锁定, 因为指标尚未达到平仓的必要值。我们称之为 "正回撤", 其最大值发送到报告。现在, 我们知道篮子的潜在利润, 以及移向积极一面的能力。
我们将此值添加到 "测试器结果" 表格的倒数第二列。"回撤/盈利" 列现在称为 "回撤/+回撤/盈利"。列的每个单元格的数据按照以下顺序排列: 回撤/正回撤/盈利。所有数据均以存款货币为单位。
此外, 最大正向回撤显示在总结果表格中。我们来引入一个额外的倒数第二 "最大正回撤" 列, 并显示整个测试期间的最大正向回撤。
下一个 EA 版本的源代码可以在下面附带的 testBasket1.mq5 文件中找到。
所获 NZD 篮子报告在 DocNZD.zip 存档中。结论如下:
- 因此, 确认了较早提出的超买/超卖价位的假设。NZDUSD_H1_1.html 报告已添加到存档中, 其级别接近可能的行盘走势开始以及大量的假入场信号。后果非常明显。
- 确定了 D1 上这种形态的交易数量很少。
- H1 的结果也令人失望。我们可以假设时间帧的 "噪音级别" 是所有篮子货币对导致的假信号总和。
我们用剩下的篮子货币结束我们的研究: AUD, EUR, GBP, CAD, JPY 和 CHF。在附件 Doc.zip 存档中查找有关这些货币的报告。现在是时候总结结果了。
结果
- 超买等级实际上在 60-70% 的范围内, 而超卖等级在 -60% 和 -70% 之间。该假设由所获得的报告和标准 WPR 指标上相应级别的位置确认。
- 查验已在三个时间帧和八个篮子上进行。我们分析了两种超买/超卖线的突破形态:
- 形态的点 1 高于 80%。超卖等级为 70%。点 2 位于 70% 和 50% 之内。当指标 ≤ 0% 是篮子平仓。这里显示了入场卖出篮子的形态形式。买入入场的形式与负值对称。
- 形态的点 1 高于 70%。超卖等级在 60%。点 2 位于 60% 和 50% 之间。当指标 ≤ 0% 是篮子平仓。这里显示了入场卖出篮子的形态形式。篮子买入入场的形式与负值对称。
- 我应当再次指出, D1 的交易很少, 本文不再提及。我们使用来自所有报告的 总结果 表格中的数据来形成其它时间帧的汇总表。该表格显示了 EA 以存款货币针对每个货币篮子进行交易, 以及上一段所描述的两种形态形式的交易结果:
H1 时间帧 AUD EUR USD GBP NZD CAD CHF JPY 形态 1 -590 90 -37 -991 -141 -80 -118 -514 形态 2 -259 -67 328 -714 -352 -446 -118 -272 H4 时间帧 AUD EUR USD GBP NZD CAD CHF JPY 形态 1 286 -72 189 -400 104 60 -209 120 形态 2 -208 25 40 80 172 10 -69 -176
- H4 的结果更有前途。从现在开始, 我们会特别注意这个时间帧。
获得的结果不允许我们在两种形式的形态之间进行选择, 所以我们必须与两者一起工作。第二种形式的潜力对我来说似乎更大, 但这只是我的主观意见。我们稍后会做出最后的决定。
我们应该在 H1 完成我们的工作吗?不可以!您可能记得, 我们已在报告中引入了 "正回撤" 参数。将其与常规回撤比较我们可以看出如下:
- 根据组合 WPR 读数 (当其值达到零时) 将篮子平仓的初始想法有所不足。限制亏损的难题还没有解决。由于我们处理一篮子订单, 因此按照存款货币分配止损和尾随止盈是合乎逻辑的。这将令我们防止在盈利时遭受即时亏损, 另一方面允许我们以合理的方式限制潜在的亏损。这种方法可导致 H1 上的积极结果, 并提高 H4 的盈利能力。而这并非我们现行计划的一部分, 但所提出的技术解决方案可能在将来是有用的。
结论
形态测试的第一阶段已经完成。获得的结果需要认真研究。在我们即将到来的工作中, 我们将集中精力整理信号, 并分析来自其它已知形态的附加信号。这将令我们获得新的信息, 并逐步完善已经获得的数据。
本文中使用的程序:
# | 名称 |
类型 |
描述 |
---|---|---|---|
1 | Pair.zip | 存档 |
计算所有篮子货币在三个选定时间帧内可能的交易数量的结果。 |
2 |
testBasket.mq5 | 智能交易系统 |
用于测试的智能交易系统。 |
3 |
DocUSD.zip | 存档 | 有关 testBasket.mq5 EA 针对 USD 篮子进行操作的 Html 报告。 |
4 | DocNZD.zip | 存档 | 有关 testBasket1 EA 针对 NZD 篮子进行操作的 Html 报告。 |
5 |
testBasket1.mq5 | 智能交易系统 | 用于测试的 EA - 下一版本。 |
6 | Doc.zip | 存档 |
有关 testBasket1 EA 针对其它篮子进行操作的 Html 报告。 |
7 | test.zip |
存档 | 含有 testIndexZig-Zag1.mq5 和 testWPReur.mq5 指标的存档。 |