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. 离差平均值DEA:DIFF的指数移动平均,移动窗口通常取9;
5. MACD:2 *(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各项指标,长短线EMA,DIFF,DEA,MACD柱。
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列,令其为0,A11、A12是在经过一系列操作后,用索引获取A6的数据后对A6中的signal修改,这是因为SPL中数据有离散型,记录可以游离于序表之外,对着游离记录修改后,原数据同样会修改,思路简单,代码可以按这种正常思路实现。而在Python中DataFrame通常会被复制,对着复制后的DataFrame修改没有用,想要修改原数据只能获取要修改数据的索引后,再对着原数据修改字段值,这样写出的代码就不是正常思路了,需要绕一下。
A13:如果没仓位且出现买入信号,买入100股,否则不买;如果有仓位且出现卖出信号,卖出100股,否则不卖。
根据以上数据画出MACD图(为了展示清楚,这里只展示了部分数据):
图中上半部分是股价和长短线的EMA,下半部分是MACD的DIFF、DEA和MACD红绿柱。红色圆点是金叉,绿色方点是死叉。
同样的数据还能画出买卖信号图:
红色圆点是买入点,绿色方点是卖出点。
和《海龟策略》一样,计算出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 |