目录
MACD的中文为平滑异同移动平均线,为一种判断股价中长期趋势的指标。当快线(DIF)由下而上穿越慢线(MACD)时,代表股价有上涨的动能存在;反之快线(DIF)向下跌破慢线(MACD)时,代表股价下跌的机率相对高。由于此类型的指标有明确的买卖讯号,所以非常适用利用回测来验证该策略表现。
而一个有效的回测,除了有明确的买卖点之外,买卖股票的成本也必须考量到买卖讯号产生的时机、手续费与证交税,甚至是最低消费20元的限制,计算报酬率时亦要考虑手中现金部位,才会最贴近实际采用该策略的表现!
本文使用 Windows OS 并以 Jupyter Notebook 作为编辑器
#基本功能 import numpy as np import pandas as pd
#绘图 import plotly.graph_objects as go from plotly import subplots
#TEJ import tejapi tejapi.ApiConfig.api_key = "Your Key" tejapi.ApiConfig.ignoretz = True
stock_data = tejapi.get('TRAIL/TAPRCD', coid= '3481', mdate={'gte': '2020-01-01', 'lte': '2020-12-31'}, opts={'columns': ['coid', 'mdate', 'open_d','close_d']}, chinese_column_name=True, paginate=True)
本文以捞取以群创光电(3481)于2020年间股价做为示范,栏位选择开盘价与收盘价,前者计算买卖价格与成本,后者用于计算买卖讯号
stock_data['12_ema'] = stock_data['收盘价(元)'].ewm(span = 12).mean() stock_data['26_ema'] = stock_data['收盘价(元)'].ewm(span = 26).mean() stock_data['dif'] = stock_data['12_ema'] - stock_data['26_ema'] stock_data['macd'] = stock_data['dif'].ewm(span = 9).mean()
接下来以收盘价计算MACD。首先利用 ewm().mean()
的方式计算12天与26天的指数移动平均线,两者之间的差即为快线DIF,再以此快线计算9日指数移动平均,则可以得到慢线MACD
stock_data['买卖股数'] = 0
#如果黄金交叉,隔天开盘买进 stock_data['买卖股数'] = np.where((stock_data['dif'].shift(1)>stock_data['macd'].shift(1)) & (stock_data['dif'].shift(2)<stock_data['macd'].shift(2)), 1000, stock_data['买卖股数'])
#如果死亡交叉,隔天开盘卖出 stock_data['买卖股数'] = np.where((stock_data['dif'].shift(1)<stock_data['macd'].shift(1)) & (stock_data['dif'].shift(2)>stock_data['macd'].shift(2)), -1000, stock_data['买卖股数'])
有了慢线与快线之后,即可建立买卖股数讯号。当两日前的DIF仍小于MACD线stock_data[‘dif’].shift(2)<stock_data[‘macd’].shift(2)
,但却于昨日DIF大于MACD stock_data[‘dif’].shift(1)>stock_data[‘macd’].shift(1)
,代表昨日出现黄金交叉买点,而因为这个讯号于昨日收盘才能确定,因此于今日开盘才购买1000股,若不符合买点条件,则维持原栏位内容stock_data[‘买卖股数’]
。同理,出现死亡交叉时,亦是隔日开盘时才卖出1000股。
stock_data['手续费'] = stock_data['开盘价(元)']* abs(stock_data['买卖股数'])*0.001425 stock_data['手续费'] = np.where((stock_data['手续费']>0)&(stock_data['手续费'] < 20), 20, stock_data['手续费']) stock_data['证交税'] = np.where(stock_data['买卖股数']<0, stock_data['开盘价(元)']* abs(stock_data['买卖股数'])*0.003, 0) stock_data['摩擦成本'] = (stock_data['手续费'] + stock_data['证交税']).apply(np.floor)
摩擦成本包含了手续费(0.1425%)与证交税(0.3%)。当买入股票时,所需负担的是手续费,但要注意的是如果券商没有提供相关优惠,则往往会有低消20元限制;而卖出时要负担的是手续费与证交税。
stock_data['股票价值'] = stock_data['买卖股数'].cumsum() * stock_data['收盘价(元)'] stock_data['现金价值'] = 10000 - stock_data['摩擦成本'] + (stock_data['开盘价(元)']* -stock_data['买卖股数']).cumsum() stock_data['资产价值'] = stock_data['股票价值'] + stock_data['现金价值']
假设初始现金有10000元台币,资产价值为股票部位价值与现金部位价值的加总。股票价值随著股票持有数量、股价波动;现金价值则随著买卖操作变动,买入股价时减少,卖出时增加,并考虑摩擦成本。
stock_data['当日价值变动(%)'] = (stock_data['资产价值']/stock_data['资产价值'].shift(1) - 1)*100 stock_data['累计报酬(%)'] = (stock_data['资产价值']/10000 - 1)*100
接著即可利用资产价值的每日变化,计算每日报酬率;也能以初始现金计算累计报酬。
可以看到MACD策略的确可以掌握部分波段,但在盘整时有反复买卖的风险,仍需特别注意。
可以看到采用MACD策略,资产价值每日的波动在8%上下以内,若波动为0代表目前手中仅有现金部位。最后的累计报酬约为63%,优于同时期0050约30%左右报酬。
读者应该可以发现,当买卖点触发过于频繁时,则累积的摩擦成本不容小觑,导致表现结果可能还不如期初买入并持有。而再反复调整初始资金后也能发现资产配置的重要性,因为当手中现金部位过大时,会拉低整体的报酬率,但相对地每日价值波动幅度也较低。其实如果要贴近现实的回测,也必须考虑持有期间的除权息,但本文为了搭配当时股价显示买卖点,才选择采用未调整的股价计算报酬率;若操作方向为做空的话,更需要考虑保证金问题,而本文首笔交易为买入,故皆为一买一卖操作。若读者想回测更长的区间、使用调整后的股价,推荐到 TEJ E-Shop 挑选最适合的方案!