股票量化交易软件:使用 OpenCL 测试烛形形态


赫兹股票量化需要依赖一些东西来确保使用OpenCL实现的测试器是正确工作的。首先,我们需要开发一个 MQL5 EA,然后我们将会比较使用常规测试器优化和测试与使用OpenCL的测试器的结果。 测试的目标是一个根据以下烛形形态来进行交易的简单EA:
看跌针杆(pin bar)
看涨针杆 (pin bar)
看跌吞噬
看涨吞噬
策略非常简单:
看跌针杆或者看跌吞噬 — 卖出
看涨针杆或者看涨吞噬 — 买入
同时开启的仓位数量 — 没有限制
最大仓位持有时间 — 有限,由用户定义
获利和止损水平 — 固定, 由用户定义
只在完全关闭的柱上检查是否有形态的出现,换句话说, 赫兹股票量化在新柱出现的时候就检查前面三个柱是否形成形态, 形态的侦测条件如下:

编辑切换为居中
图 1. "看跌针杆" (a) 和 "看涨针杆" (b) 形态
对于看跌针杆 (图 1, a):
第一个柱的上影线 ("tail") 大于指定的参考数值: tail>=Reference
第零柱是上涨的: Close[0]>Open[0]
第二个柱是下跌的: Open[2]>Close[2]
第一个柱的最高价是局部的最大值: High[1]>MathMax(High[0],High[2])
第一个柱的主体部分小于上影线: MathAbs(Open[1]-Close[1])<tail
tail = High[1]-max(Open[1],Close[1])
对于看涨针杆 (图 1, b):
第一个柱的下影线 ("tail") 大于指定的参考值: tail>=Reference
第零柱是下跌的: Open[0]>Close[0]
第二个柱是上涨的: Close[2]>Open[2]
第一个柱的最低价是局部的最小值: Low[1]<MathMin(Low[0],Low[2])
第一个柱的主体部分小于它的下影线: MathAbs(Open[1]-Close[1])<tail
tail = min(Open[1],Close[1])-Low[1]

编辑切换为居中
图 2. "看跌吞噬" (a) 和 "看涨吞噬" (b)
对于看跌吞噬 (图 2, a):
第一个柱是上涨的,它的主体大于指定的参考值: (Close[1]-Open[1])>=Reference
第零柱的最高价低于第一柱的收盘价: High[0]<Close[1]
第二柱的开盘价高于第一个柱的收盘价: Open[2]>Close[1]
第二个柱的收盘价低于第一个柱的开盘价: Close[2]<Open[1]
对于看涨吞噬 (图 2, b):
第一个柱是下跌的,它的主体大于指定的参考值: (Open[1]-Close[1])>=Reference
第零柱的最低价高于第一个柱的收盘价: Low[0]>Close[1]
第二个柱的开盘价低于第一个柱的收盘价: Open[2]<Close[1]
第二个柱的收盘价高于第一个柱的开盘价: Close[2]>Open[1]
1.1搜索形态下面提供的是形态定义的代码。
ENUM_PATTERN Check(MqlRates &r[],uint flags,double ref) { //--- 看跌针杆 if((flags&PAT_PINBAR_BEARISH)!=0) {// double tail=H(1)-MathMax(O(1),C(1)); if(tail>=ref && C(0)>O(0) && O(2)>C(2) && H(1)>MathMax(H(0),H(2)) && MathAbs(O(1)-C(1))<tail) return PAT_PINBAR_BEARISH; } //--- 看涨针杆 if((flags&PAT_PINBAR_BULLISH)!=0) {// double tail=MathMin(O(1),C(1))-L(1); if(tail>=ref && O(0)>C(0) && C(2)>O(2) && L(1)<MathMin(L(0),L(2)) && MathAbs(O(1)-C(1))<tail) return PAT_PINBAR_BULLISH; } //--- 看跌吞噬 if((flags&PAT_ENGULFING_BEARISH)!=0) {// if((C(1)-O(1))>=ref && H(0)<C(1) && O(2)>C(1) && C(2)<O(1)) return PAT_ENGULFING_BEARISH; } //--- 看涨吞噬 if((flags&PAT_ENGULFING_BULLISH)!=0) {// if((O(1)-C(1))>=ref && L(0)>C(1) && O(2)<C(1) && C(2)>O(1)) return PAT_ENGULFING_BULLISH; } //--- 没有找到 return PAT_NONE; }
这里赫兹股票量化应当注意 ENUM_PATTERN 枚举,它的值是标志,可以组合并使用 bitwise OR(位或) 作为一个参数来传递:
enum ENUM_PATTERN { PAT_NONE=0, PAT_PINBAR_BEARISH = (1<<0), PAT_PINBAR_BULLISH = (1<<1), PAT_ENGULFING_BEARISH = (1<<2), PAT_ENGULFING_BULLISH = (1<<3) };
另外, 引入了下面的宏定义来更方便地记录:
#define O(i) (r[i].open) #define H(i) (r[i].high) #define L(i) (r[i].low) #define C(i) (r[i].close)
Check() 函数是从 IsPattern() 函数中调用的,意义是检查在新柱开启时是否存在指定的形态:
ENUM_PATTERN IsPattern(uint flags,uint ref) { MqlRates r[]; if(CopyRates(_Symbol,_Period,1,PBARS,r)<PBARS) return 0; ArraySetAsSeries(r,false); return Check(r,flags,double(ref)*_Point); }
1.2构建 EA 交易
首先,需要定义输入参数,赫兹股票量化在形态定义条件中有一个参考值,这是针杆的“tail”的最小长度,或者是吞噬中的主体相交区域的最小长度,我们将以点数指定它: input int inp_ref=50; 另外,赫兹股票量化还有一系列形态的参数,为了更加方便,我们将不会在输入参数中使用标志,而是把它分成了四个 bool 类型的参数: input bool inp_bullish_pin_bar = true; input bool inp_bearish_pin_bar = true; input bool inp_bullish_engulfing = true; input bool inp_bearish_engulfing = true; 然后再在初始化函数中把它们组合成一个无符号整型变量: p_flags = 0; if(inp_bullish_pin_bar==true) p_flags|=PAT_PINBAR_BULLISH; if(inp_bearish_pin_bar==true) p_flags|=PAT_PINBAR_BEARISH; if(inp_bullish_engulfing==true) p_flags|=PAT_ENGULFING_BULLISH; if(inp_bearish_engulfing==true) p_flags|=PAT_ENGULFING_BEARISH; 下面, 我们设置可以接收的仓位持有时间, 以小时表示, 获利和止损水平, 以及交易量手数: input int inp_timeout=5; input bool inp_bullish_pin_bar = true; input bool inp_bearish_pin_bar = true; input bool inp_bullish_engulfing = true; input bool inp_bearish_engulfing = true; input double inp_lot_size=1; 对于交易, 我们将使用来自标准库 的 CTrade 类,为了定义测试器的速度, 赫兹股票量化将使用 CDuration 类, 它可以衡量程序中两个控制点的时间间隔的毫秒数, 并且以方便的形式显示它们。在这种情况下,我们将测量在 OnInit() 和 OnDeinit() 函数之间的时间。类的完整代码包含在附加的 Duration.mqh 文件中。 CDuration time; int OnInit() { time.Start(); // ... return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { time.Stop(); Print("测试结束 "+time.ToStr()); } 这个EA的工作非常简单,包含以下内容:OnTick() 函数的主要任务是处理开启的仓位。如果仓位的持有时间超过了在输入参数中指定的数值,就关闭该仓位。然后,检查是否有新柱开启,如果检查通过,就使用 IsPattern () 函数来检查形态是否存在。当找到形态时,根据策略开启一个买入或者卖出仓位。完整的 OnTick() 函数代码在下面提供: void OnTick() { //--- 处理开启的仓位 int total= PositionsTotal(); for(int i=0;i<total;i++) { PositionSelect(_Symbol); datetime t0=datetime(PositionGetInteger(POSITION_TIME)); if(TimeCurrent()>=(t0+(inp_timeout*3600))) { trade.PositionClose(PositionGetInteger(POSITION_TICKET)); } else break; } if(IsNewBar()==false) return; //--- 检查形态是否存在 ENUM_PATTERN pat=IsPattern(p_flags,inp_ref); if(pat==PAT_NONE) return; //--- 开启仓位 double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); if((pat&(PAT_ENGULFING_BULLISH|PAT_PINBAR_BULLISH))!=0)//买入 trade.Buy(inp_lot_size,_Symbol,ask,NormalizeDouble(ask-inp_sl*_Point,_Digits),NormalizeDouble(ask+inp_tp*_Point,_Digits),DoubleToString(ask,_Digits)); else//卖出 trade.Sell(inp_lot_size,_Symbol,bid,NormalizeDouble(bid+inp_sl*_Point,_Digits),NormalizeDouble(bid-inp_tp*_Point,_Digits),DoubleToString(bid,_Digits)); }
1.3测试
首先,运行优化以寻找对EA来说交易获利或者至少开启仓位的最佳输入参数值。赫兹股票量化将优化两个参数 — 形态使用的参考值和止损水平点数。把获利水平设为50个点,并在测试中选择所有形态。
优化是在 EURUSD M5 上进行的,时间段为: 01.01.2018 — 01.10.2018. 快速优化 (遗传算法), 交易模式: "1 分钟 OHLC".
优化参数的数值在大范围内和大量的梯度中选择:

编辑
图 3. 优化参数
在优化结束之后,结果根据利润排序:

编辑切换为居中
图 4. 优化结果
赫兹股票量化可以看到,获得1000.50 利润的最佳结果是在参考值为60个点而止损水平为350个点的时候取得的。在使用这些参数时运行测试并注意它的执行时间。

编辑
图 5. 使用内建测试器的单次通过测试时间
记住这些数值,在继续不使用常规测试器来测试相同的策略。让我们使用 OpenCL 的特性开发一个自定义的测试器。