SPL 量化系列实践:MACD 背离策略

MACD从均线指标EMA衍化而来,对把握趋势性行情有着很好的应用效果,它的顶底背离是一种经过检验的“抄底逃顶”方法,是不少中长期投资者在实战中都会考虑的指标。本文就以SPL来实现MACD背离策略,并进行回测。

在实现代码之前还是要介绍下MACD是怎么计算的:

1. 短线EMA:短线的指数移动平均,移动窗口通常取12

EMA=q*收盘价+(1-q)* EMA[-1]

其中q是平滑系数,q=2/(移动窗口+1),这里的收盘价是后复权的收盘价。

后续的EMA都是这样计算,只是移动窗口取值不同。

2. 长线EMA:长线的指数移动平均,移动窗口通常取26

3. 长短线的离差DIFF:短线EMA-长线EMA

4. 离差平均值DEADIFF的指数移动平均,移动窗口通常取9

5. MACD2 *(DIFF - DEA)

下面介绍MACD背离买卖策略:

金叉:DIFF上穿DEA

死叉:DIFF下穿DEA

底背离:股价创新低但DIFF没有新低。把最近一次死叉到金叉的区间称为区间1,把再前一次死叉到金叉的区间称为区间2。区间1的最低股价小于区间2的最低股价,区间1的最低DIFF大于区间2的最低DIFF,此时的金叉作为买入信号。

顶背离:股价创新高但DIFF没有新高。把最近一次金叉到死叉的区间称为区间3,把再前一次金叉到死叉的区间称为区间4。区间3的最高股价大于区间4的最高股价,区间3的最高DIFF小于区间4的最高DIFF,此时的死叉作为卖出信号。

SPL代码


A

1

=file("daily/000026.csv").import@tc()

2

=A1.select(trade_date>=20200101&&trade_date<=20240306)

3

=A2.derive(if(#>1, close/ pre_close *factor[-1], close/pre_close):factor)

4

=hfq_fst=A3(1),A3.derive(round(factor/hfq_fst.factor*hfq_fst.close,2): hfq_close)

5

=q12=2/13,q26=2/27,q9=2/10,A4.new(ts_code,trade_date,hfq_close,if(#==1,hfq_close,q12*hfq_close+(1-q12)*EMA12[-1]):EMA12,if(#==1,hfq_close,q26*hfq_close+(1-q26)*EMA26[-1]):EMA26,EMA12-EMA26:DIFF,if(#==1,DIFF,q9*DIFF+(1-q12)*DEA[-1]):DEA,2*(DIFF-DEA):MACD)

6

=lth=A5.len(),A5.derive(if(#==1,0,if(DIFF>DEA&&DIFF[-1]<DEA[-1],1,if(DIFF<DEA&&DIFF[-1]>DEA[-1],-1,0))):gcross_or_dcross,0:signal)

7

=A6.pselect@a(gcross_or_dcross!=0)

8

=A7.(if(#==1,,to(~[-1],~))).to(2,)

9

=A8.group((#-1)%2)

10

=s=A6(A7(1)).gcross_or_dcross,if(s==1,A9.rvs(),A9)

11

=A10(1).(A6(~)).((itv1=~,itv2=~[-1],if(#>1&&itv1.min(hfq_close)<itv2.min(hfq_close)&&itv1.min(DIFF)>itv2.min(DIFF),~.m(-1).signal=1)))

12

=A10(2).(A6(~)).((itv1=~,itv2=~[-1],if(#>1&&itv1.max(hfq_close)>itv2.max(hfq_close)&&itv1.max(DIFF)<itv2.max(DIFF),~.m(-1).signal=-1)))

13

=ps=0,A6.derive(if(ps==0&&signal==1,(ps=1,1),if(ps!=0&&signal==-1,(ps=0,-1),0)):flag,if(flag!=0,100,0):shares)

A4:对收盘价后复权。

A5:计算MACD各项指标,长短线EMADIFFDEAMACD柱。

..

A6:计算金叉和死叉,金叉时,gcross_or_dcross=1,死叉时,gcross_or_dcross=-1,其余时间为0,同时增加一个买卖信号标志signal,先令signal都为0

..

A7:找到金叉或死叉的位置。

A8:把他们分成金叉到死叉或者死叉到金叉的区间。

A9:如果金叉到死叉区间是单数位置,则死叉到金叉区间是偶数位置,反之则反。

A10:如果第一组是金叉到死叉区间,则把两组位置颠倒,否则不变,这样做保证第一组是死叉到金叉区间。

A11:根据底背离规则确定买卖信号,如果底背离出现,相应的金叉位置signal=1

A12:同理,确定顶背离信号,signal=-1。修改signal信号后的数据如下:

..

之前A6中,我们只增加了signal列,令其为0A11A12是在经过一系列操作后,用索引获取A6的数据后对A6中的signal修改,这是因为SPL中数据有离散型,记录可以游离于序表之外,对着游离记录修改后,原数据同样会修改,思路简单,代码可以按这种正常思路实现。而在PythonDataFrame通常会被复制,对着复制后的DataFrame修改没有用,想要修改原数据只能获取要修改数据的索引后,再对着原数据修改字段值,这样写出的代码就不是正常思路了,需要绕一下。

A13:如果没仓位且出现买入信号,买入100股,否则不买;如果有仓位且出现卖出信号,卖出100股,否则不卖。

..

根据以上数据画出MACD图(为了展示清楚,这里只展示了部分数据):

..

图中上半部分是股价和长短线的EMA,下半部分是MACDDIFFDEAMACD红绿柱。红色圆点是金叉,绿色方点是死叉。

同样的数据还能画出买卖信号图:

..

红色圆点是买入点,绿色方点是卖出点。

和《海龟策略》一样,计算出flag后就可以回测了,过滤出A13数据中flag不为0的行,调用回测接口即可。

回测收益率图如下:

..

回测指标见下表:

indicators

value

累计收益率 (cumulative rate of return)

82.22%

年化收益率 (annualized rate of return)

17.23%

年化波动率 (annual volatility)

50.06%

夏普比率 (sharpe ratio)

0.28

最大回撤 (maximum drawdown)

44.18%

投入现金 (cash invested)

883.01

总资产 (total assets)

1609.01

仓位占比 (stock holding ratio)

0.0%

盈利次数 (profit times)

3

亏损次数 (losses times)

0