A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

01 引言

《易經》早就揭示出:物極必反,盛極必衰!陰陽總是不斷交替的。股票市場也一樣,漲跌互現,漲多了會出現調整,跌多了會出現反彈,因此我們看到K線組合總是紅(陽)綠(陰)相間的。正是由於市場行情總是陰陽交替出現,交易者們才孜孜不倦地想通過擇時(選股)來獲取超額收益。指數的走勢是各方資金博弈的結果,而博弈的過程存在一個時間的延續性,也就是說過去的走勢對未來走向有一定的參考價值。儘管過去不能代表未來,但統計發現歷史總是“驚人的相似”,比如“月份效應”。實際上,不少實證研究發現大多數市場存在“月份效應”,即存在某個或某些特定月份的平均收益率年復一年顯著地異於其他各月平均收益率的現象。

推文《A股指數圖譜:是否有月份效應?》對A股歷史走勢、漲跌頻率和“月份效應”進行了初步的量化分析和統計檢驗,發現各大指數在2月份具有統計上顯著的正收益。本文在此基礎上,對指數月度收益率及其波動性進行統計分析,根據指數月度收益率的歷史表現構建簡單的月度擇時策略並進行歷史回測。

02月度收益率分析

數據獲取


使用tushare在線獲取指數(股票)日收益率數據,考慮到完整月份,數據期間選取2000年1月1日至2019年12月31日。

<code>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/<code>
<code>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/<code>

月度收益率情況


對指數月度收益率進行可視化分析,標註收益率高於四分之三分位數的點。

<code>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()/<code>

從圖中不難看出,上證綜指和創業板指數月度收益率圍繞均線上下波動。

<code>plot_mnthly_ret('sh','上證綜指')/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>plot_mnthly_ret('cyb','創業板')/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

月波動率情況


實證研究表明,收益率標準差(波動率)存在一定的集聚現象,即高波動率和低波動率往往會各自聚集在一起,並且高波動率和低波動率聚集的時期是交替出現的。

<code>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()/<code>

圖中紅色部門顯示出,上證綜指和創業板指數均存在一定的波動集聚現象。

<code>plot_votil('sh','上證綜指')/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>plot_votil('cyb','創業板')/<code>


月收益率均值


下面對月度收益率均值進行統計分析,圖中顯示某些月份具有正的收益率均值,而某些月份具有負的收益率均值,比如上證綜指2月、3月、4月、11月、12月收益率均值大於1%,而6月和8月收益率均值小於-1%;創業板情況類似,但某些月份存在一定差異。

<code>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/<code>
<code>plot_mean_ret('sh','上證綜指')/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>plot_mean_ret('cyb','創業板')/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」


03月度擇時策略回測

根據第二部分的統計分析,構建一個簡單的月度擇時策略並進行歷史回測。即先對指數歷史數據進行統計分析,計算月度收益率的歷史均值,當月度收益率均值大於1%時做多改月,當月度收益率均值小於-1%時做空改月。


計算收益率均值

計算滿足做多做空條件的月份,其餘月份相當於空倉。

<code>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']/<code>


策略構建

<code>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/<code>


回測評價指標

<code>def performance(df):
     #代碼較長,此處略/<code>
<code>#將上述函數整合成一個執行函數
def main(code='sh',name='上證綜指',is_short=False):
    df=Month_Strategy(code,is_short)
    print(f'回測標的:{name}指數')
    performance(df)
    plot_performance(df,name)/<code>


回測結果


上證綜指情況:

<code>#默認回測標的是上證綜指
main()
#不能做空時,結果如下:/<code>
<code>做多月份:[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/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>main(is_short=True)
#可以做空時,結果如下:/<code>
<code>做多月份:[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/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

創業板指數情況:

<code>main('cyb','創業板')
#不能做空時,結果如下:/<code> 
<code>做多月份:[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/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>main('cyb','創業板',is_short=True)
#可以做空時,結果如下:/<code>
<code>做多月份:[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/<code> 
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

<code>main('zxb','中小板')
#不能做空時,結果如下:/<code>
<code>做多月份:[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/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」

中小板指數情況:

<code>main('zxb','中小板',is_short=True)
#可以做空時,結果如下:/<code>
<code>做多月份:[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/<code>
A股存在月份效應嗎?構建月度擇時策略「附Python源碼」


04 結語

本文根據指數歷史月度收益率的統計發現,某些月份具有正的收益率均值,而某些月份具有負的收益率均值,因此通過設定某個閾值進行擇時,當月度收益率均值大於1%時做多改月,當月度收益率均值小於-1%時做空該月,從而構建了一個簡單的月度擇時策略。當然,這一策略存在一定的侷限性,比如:(1)使用月度收益率樣本量偏小,可能存在一定的偏差;(2)相當於使用了樣本內數據進行擬合,可能存在過擬合問題。感興趣的讀者可以將樣本分成兩部分進一步考察,如2000-2016作為訓練,2018-2020作為測試;(3)當市場環境和交易習慣發生較大變化後(如取消漲跌停板或延長交易時間等),過去的統計規律可能會失效等。本文對月度收益率進行統計分析並構建擇時策略旨在於拋磚引玉,為大家考察和分析市場提供一個思路或角度,同時為大家熟練使用Python進行金融量化研究提供一個參考案例,以上分析不構成任何投資建議。


分享到:


相關文章: