A股存在月份效应吗?构建月度择时策略【附Python源码】

01 引言
《易经》早就揭示出:物极必反,盛极必衰!阴阳总是不断交替的。股票市场也一样,涨跌互现,涨多了会出现调整,跌多了会出现反弹,因此我们看到K线组合总是红(阳)绿(阴)相间的。正是由于市场行情总是阴阳交替出现,交易者们才孜孜不倦地想通过择时(选股)来获取超额收益。指数的走势是各方资金博弈的结果,而博弈的过程存在一个时间的延续性,也就是说过去的走势对未来走向有一定的参考价值。尽管过去不能代表未来,但统计发现历史总是“惊人的相似”,比如“月份效应”。实际上,不少实证研究发现大多数市场存在“月份效应”,即存在某个或某些特定月份的平均收益率年复一年显著地异于其他各月平均收益率的现象。公众号推文《A股指数图谱:是否有月份效应?》对A股历史走势、涨跌频率和“月份效应”进行了初步的量化分析和统计检验,发现各大指数在2月份具有统计上显著的正收益。本文在此基础上,对指数月度收益率及其波动性进行统计分析,根据指数月度收益率的历史表现构建简单的月度择时策略并进行历史回测。

02 月度收益率分析

数据获取

使用tushare在线获取指数(股票)日收益率数据,考虑到完整月份,数据期间选取2000年1月1日至2019年12月31日。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
%matplotlib inline

#正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
import tushare as ts
def get_daily_ret(code='sh',start='2000-01-01',end='2019-12-31'):
    df=ts.get_k_data(code,start=start,end=end)
    df.index=pd.to_datetime(df.date)
    #计算日收益率
    daily_ret = df['close'].pct_change()
    #删除缺失值
    daily_ret.dropna(inplace=True)
    return daily_ret


月度收益率情况

对指数月度收益率进行可视化分析,标注收益率高于四分之三分位数的点。

def plot_mnthly_ret(code,title):
    daily_ret = get_daily_ret(code)
    #月度收益率
    mnthly_ret = daily_ret.resample('M').apply(lambda x : ((1+x).prod()-1))
    #可视化
    plt.rcParams['figure.figsize']=[20,5]
    mnthly_ret.plot()
    start_date=mnthly_ret.index[0]
    end_date=mnthly_ret.index[-1]
    plt.xticks(pd.date_range(start_date,end_date,freq='Y'),[str(y) for y in range(start_date.year+1,end_date.year+1)])
    #显示月收益率大于3/4分位数的点
    dates=mnthly_ret[mnthly_ret>mnthly_ret.quantile(0.75)].index   
    for i in range(0,len(dates)):
        plt.scatter(dates[i], mnthly_ret[dates[i]],color='r')
    labs = mpatches.Patch(color='red',alpha=.5, label="月收益率高于3/4分位")
    plt.title(title+'月度收益率',size=15)
    plt.legend(handles=[labs])
    plt.show()

从图中不难看出,上证综指和创业板指数月度收益率围绕均线上下波动。

plot_mnthly_ret('sh','上证综指')
plot_mnthly_ret('cyb','创业板')

月波动率情况


实证研究表明,收益率标准差(波动率)存在一定的集聚现象,即高波动率和低波动率往往会各自聚集在一起,并且高波动率和低波动率聚集的时期是交替出现的。

def plot_votil(code,title):
    #月度收益率的年化标准差(波动率)
    daily_ret=get_daily_ret(code)
    mnthly_annu = daily_ret.resample('M').std()* np.sqrt(12)
    plt.rcParams['figure.figsize']=[20,5]
    mnthly_annu.plot()
    start_date=mnthly_annu.index[0]
    end_date=mnthly_annu.index[-1]
    plt.xticks(pd.date_range(start_date,end_date,freq='Y'),[str(y) for y in range(start_date.year+1,end_date.year+1)])
    dates=mnthly_annu[mnthly_annu>0.07].index
    for i in range(0,len(dates)-1,3):
        plt.axvspan(dates[i],dates[i+1],color='r',alpha=.5)
    plt.title(title+'月度收益率标准差',size=15)
    labs = mpatches.Patch(color='red',alpha=.5, label="波动集聚")
    plt.legend(handles=[labs])
    plt.show()

图中红色部门显示出,上证综指和创业板指数均存在一定的波动集聚现象。

plot_votil('sh','上证综指')
plot_votil('cyb','创业板')


月收益率均值


下面对月度收益率均值进行统计分析,图中显示某些月份具有正的收益率均值,而某些月份具有负的收益率均值,比如上证综指2月、3月、4月、11月、12月收益率均值大于1%,而6月和8月收益率均值小于-1%;创业板情况类似,但某些月份存在一定差异。

from pyecharts import Bar
#pyecharts是0.5.11版本
def plot_mean_ret(code,title):
    daily_ret = get_daily_ret(code)
    #月度收益率
    mnthly_ret = daily_ret.resample('M').apply(lambda x : ((1+x).prod()-1))
    mrets=(mnthly_ret.groupby(mnthly_ret.index.month).mean()*100).round(2) 
    attr=[str(i)+'月' for i in range(1,13)]
    v=list(mrets)
    bar=Bar(title+'月平均收益率%')
    bar.add('',attr,v,
       is_label_show=True)
    return bar
plot_mean_ret('sh','上证综指')
plot_mean_ret('cyb','创业板')


03月度择时策略回测


根据第二部分的统计分析,构建一个简单的月度择时策略并进行历史回测。即先对指数历史数据进行统计分析,计算月度收益率的历史均值,当月度收益率均值大于1%时做多改月,当月度收益率均值小于-1%时做空改月。


计算收益率均值

计算满足做多做空条件的月份,其余月份相当于空仓。

def month_ret_stats(code):
    daily_ret = get_daily_ret(code)
    #月度收益率
    mnthly_ret = daily_ret.resample('M').apply(lambda x : ((1+x).prod()-1))
    ret_stats=mnthly_ret.groupby(mnthly_ret.index.month).describe()
    pnm=ret_stats[ret_stats['mean']>0.01].index.to_list()
    nnm=ret_stats[ret_stats['mean']<-0.01].index.to_list()
    return pnm,nnm


策略构建

def Month_Strategy(code,is_short):
    daily_ret = get_daily_ret(code)
    #月度收益率
    mnthly_ret = daily_ret.resample('M').apply(lambda x : ((1+x).prod()-1))
    #设计买卖信号
    df=pd.DataFrame(mnthly_ret.values,index=mnthly_ret.index,columns=['ret'])
    #做多月份
    pnm,nnm=month_ret_stats(code)
    print(f'做多月份:{pnm}')
    df['signal']=0
    for m in pnm:
        df.loc[df.index.month==m,'signal']=1
    #如果可以做空
    if is_short==True:
        for n in nnm:
            df.loc[df.index.month==n,'signal']=-1
        print(f'做空月份:{nnm}')

    df['capital_ret']=df.ret.mul(df.signal)
    #计算标的、策略的累计收益率
    df['策略净值']=(df.capital_ret+1.0).cumprod()
    df['指数净值']=(df.ret+1.0).cumprod()
    return df


回测评价指标

def performance(df):
    #代码较长,此处略
#将上述函数整合成一个执行函数
def main(code='sh',name='上证综指',is_short=False):
    df=Month_Strategy(code,is_short)
    print(f'回测标的:{name}指数')
    performance(df)
    plot_performance(df,name)


回测结果


上证综指情况:

#默认回测标的是上证综指
main()
#不能做空时,结果如下:
做多月份:[2, 3, 4, 11, 12]
回测标的:上证综指指数
策略年胜率为:60.0%
策略月胜率为:58.0%
总收益率:  策略:545.53%,指数:116.88%
年化收益率:策略:9.77%, 指数:3.95%
最大回撤:  策略:30.0%, 指数:70.97%
策略Alpha: 0.08, Beta:0.43,夏普比率:2.04
main(is_short=True)
#可以做空时,结果如下:
做多月份:[2, 3, 4, 11, 12]
做空月份:[6, 8]
回测标的:上证综指指数
策略年胜率为:65.0%
策略月胜率为:55.71%
总收益率:  策略:1169.63%,指数:116.88%
年化收益率:策略:13.55%, 指数:3.95%
最大回撤:  策略:41.71%, 指数:70.97%
策略Alpha: 0.13, Beta:0.22,夏普比率:2.59

创业板指数情况:

main('cyb','创业板')
#不能做空时,结果如下:
做多月份:[2, 3, 5, 10, 11]
回测标的:创业板指数
策略年胜率为:50.0%
策略月胜率为:63.83%
总收益率:  策略:344.64%,指数:80.33%
年化收益率:策略:16.85%, 指数:6.35%
最大回撤:  策略:14.99%, 指数:65.34%
策略Alpha: 0.14, Beta:0.47,夏普比率:2.08
main('cyb','创业板',is_short=True)
#可以做空时,结果如下:
做多月份:[2, 3, 5, 10, 11]
做空月份:[1, 6, 12]
回测标的:创业板指数
策略年胜率为:70.0%
策略月胜率为:64.47%
总收益率:  策略:613.55%,指数:80.33%
年化收益率:策略:22.76%, 指数:6.35%
最大回撤:  策略:34.05%, 指数:65.34%
策略Alpha: 0.22, Beta:0.19,夏普比率:2.38
main('zxb','中小板')
#不能做空时,结果如下:
做多月份:[2, 3, 5, 7, 12]
回测标的:中小板指数
策略年胜率为:76.92%
策略月胜率为:62.3%
总收益率:  策略:350.15%,指数:14.57%
年化收益率:策略:12.97%, 指数:1.11%
最大回撤:  策略:22.63%, 指数:64.77%
策略Alpha: 0.12, Beta:0.46,夏普比率:1.93

中小板指数情况:

main('zxb','中小板',is_short=True)
#可以做空时,结果如下:
做多月份:[2, 3, 5, 7, 12]
做空月份:[1, 6, 8]
回测标的:中小板指数
策略年胜率为:76.92%
策略月胜率为:58.76%
总收益率:  策略:972.68%,指数:14.57%
年化收益率:策略:21.21%, 指数:1.11%
最大回撤:  策略:29.35%, 指数:64.77%
策略Alpha: 0.21, Beta:0.16,夏普比率:2.66


04 结语

本文根据指数历史月度收益率的统计发现,某些月份具有正的收益率均值,而某些月份具有负的收益率均值,因此通过设定某个阈值进行择时,当月度收益率均值大于1%时做多改月,当月度收益率均值小于-1%时做空该月,从而构建了一个简单的月度择时策略。当然,这一策略存在一定的局限性,比如:(1)使用月度收益率样本量偏小,可能存在一定的偏差;(2)相当于使用了样本内数据进行拟合,可能存在过拟合问题。感兴趣的读者可以将样本分成两部分进一步考察,如2000-2016作为训练,2018-2020作为测试;(3)当市场环境和交易习惯发生较大变化后(如取消涨跌停板或延长交易时间等),过去的统计规律可能会失效等。本文对月度收益率进行统计分析并构建择时策略旨在于抛砖引玉,为大家考察和分析市场提供一个思路或角度,同时为大家熟练使用Python进行金融量化研究提供一个参考案例,以上分析不构成任何投资建议。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350