【量化投資】運(yùn)用Python分析公募基金
運(yùn)用Python分析公募基金
1、背景
學(xué)校財(cái)富管理課程的期末論文是分析中國(guó)各種投資標(biāo)的的收益,筆者分配到的研究的細(xì)分類別為:通過(guò)大集合轉(zhuǎn)公募基金的方式,成立的公募基金的收益分析。Python在量化投資,尤其是投資的分析、策略回測(cè)等方面有著廣泛的運(yùn)用,所以筆者結(jié)合在帥帥老師課程中學(xué)習(xí)的知識(shí),運(yùn)用Python對(duì)基金的收益進(jìn)行分析。
2、數(shù)據(jù)來(lái)源
“巧婦難為無(wú)米之炊”,尋找高質(zhì)量的數(shù)據(jù)是分析的第一步。本文的數(shù)據(jù)來(lái)自于Wind客戶端。數(shù)據(jù)分為兩個(gè):
鏈接:https://pan.baidu.com/s/1JzJWxM9CyxTotldu5BjbjA 提取碼:clki
3、數(shù)據(jù)分析
3.1 導(dǎo)入數(shù)據(jù)
def?day_data(self):
????day_data?=?pd.read_csv(root_path+'/data/大集合轉(zhuǎn)公募基金復(fù)權(quán)凈值day.CSV',?encoding='gbk')
????day_data.rename(columns={'Unnamed:?0':?'交易日期'},?inplace=True)
????day_data['交易日期']?=?pd.to_datetime(day_data['交易日期'])
????#day_rtn.set_index('交易日期',?inplace=True)
????day_data.dropna(axis=1,?how='any',?inplace=True)
????return?day_data
def?index_day_data(self):
????index_day_rtn?=?pd.read_csv(root_path+'/data/中國(guó)基金加權(quán)指數(shù)day.csv',
?????????????????????????????????encoding='gbk')
????index_day_rtn.rename(columns={'Unnamed:?0':?'交易日期'},?inplace=True)
????index_day_rtn['交易日期']?=?pd.to_datetime(index_day_rtn['交易日期'])
????index_day_rtn['中國(guó)基金加權(quán)總指數(shù)']?=?index_day_rtn['中國(guó)基金加權(quán)總指數(shù)'].pct_change()
????return?index_day_rtn
在導(dǎo)入數(shù)據(jù)時(shí),我們發(fā)現(xiàn)有許多缺失值,這是因?yàn)榇蟛糠执蠹显?021年才轉(zhuǎn)為公募基金,所以僅有幾個(gè)月的收益。我們?cè)诖颂幉扇∽詈?jiǎn)單的數(shù)據(jù)清洗方式:將含有缺失值的基金刪除。
這是整理后的數(shù)據(jù):
3.2數(shù)據(jù)信息提取
觀察數(shù)據(jù),發(fā)現(xiàn)這些公募基金的名字既長(zhǎng)又復(fù)雜,分析的時(shí)候一個(gè)一個(gè)輸入名字肯定非常費(fèi)時(shí)間。通過(guò)觀察發(fā)現(xiàn),這些基金的名字有個(gè)特點(diǎn):基金名字的前兩個(gè)或多個(gè)字,為基金公司的名字。如:海通的基金就命名為:海通量化價(jià)值精選一年持有B、海通海升六個(gè)月持有A等。
那么我們運(yùn)用正則表達(dá)式,實(shí)現(xiàn)輸入證券資管的名稱,就得到其旗下的公募基金的名稱。
def?get_col_name(self):
????day_data?=?self.day_data()
????#?匹配正則表達(dá)式
????pattern?=?re.compile('^%s'%?self.company)
????col_name?=?day_data.columns.tolist()
????choose_name?=?[]
????for?name?in?col_name:
????????if?pattern.match(name):
????????????choose_name.append(name)
????print(choose_name)
????return?choose_name
def?col_num(self):
????choose_name?=?self.get_col_name()
????num?=?len(choose_name)
????#?print(num)
????return?num
3.3 指數(shù)信息和基金信息按日期合并
def?choose_data(self):
????choose_name?=?self.get_col_name()
????day_data?=?self.day_data()
????day_data?=?day_data[['交易日期',?choose_name[self.fund_num]]]
????day_data[choose_name[self.fund_num]]?=?day_data[choose_name[self.fund_num]].pct_change()
????day_rtn?=?day_data
????#?和指數(shù)信息合并
????index_day_rtn?=?self.index_day_data()
????equity_day?=?pd.merge(day_rtn,?index_day_rtn,?on='交易日期',?how='left'?)
????equity_day.dropna(axis=0,?how='any',?inplace=True)
????equity_day.rename(columns={choose_name[self.fund_num]:?'漲跌幅',?'中國(guó)基金加權(quán)總指數(shù)':?'指數(shù)漲跌幅'},?inplace=True)
????return?equity_day
此處計(jì)算基金收益時(shí)運(yùn)用了dataframe.pct_change()函數(shù)(Pandas dataframe.pct_change()函數(shù)計(jì)算當(dāng)前元素與之前元素之間的百分比變化。默認(rèn)情況下,此函數(shù)計(jì)算前一行的百分比變化)。
此處我們使用的是環(huán)比增長(zhǎng),假如想用對(duì)數(shù)收益率,則不可以使用dataframe.pct_change()函數(shù)。
3.4畫(huà)收益率曲線
為了能夠在以后的研究中,方便調(diào)用繪畫(huà)收益率曲線的函數(shù),我們新建一個(gè)專門存放自建函數(shù)的文檔,將函數(shù)保存。
#?===繪制收益率曲線
#?繪制策略曲線
def?draw_equity_curve(df,?data_dict,?time=None,?pic_size=[16,?9],?dpi=72,?font_size=25,?save_path='fig.jpg'):
????plt.rcParams['font.sans-serif']?=?['SimHei']
????plt.rcParams['axes.unicode_minus']?=?False
????#?plt.style.use('dark_background')
????plt.figure(figsize=(pic_size[0],?pic_size[1]),?dpi=dpi)
????plt.xticks(fontsize=font_size)
????plt.yticks(fontsize=font_size)
????for?key?in?data_dict:
????????if?time:
????????????plt.plot(df[time],?df[data_dict[key]],?label=key)
????????else:
????????????plt.plot(df.index,?df[data_dict[key]],?label=key)
????plt.legend(fontsize=font_size)
????plt.grid()
????#plt.show(
????plt.savefig(save_path)
在主程序中調(diào)用繪畫(huà)收益率曲線的函數(shù):
def?yield_curve(self):
????equity?=?self.choose_data()
????equity['equity_curve']?=?(equity['漲跌幅']?+?1).cumprod()
????equity['benchmark']?=?(equity['指數(shù)漲跌幅']?+?1).cumprod()
????equity?=?equity[['交易日期',?'equity_curve',?'benchmark',?'漲跌幅',?'指數(shù)漲跌幅']]
????return?equity
def?draw_curve(self,?save_path?=?r'fig.jpg'):
????choose_name?=?self.get_col_name()
????equity?=?self.yield_curve()
????equity?=?equity.reset_index(drop=True)
????equity['交易日期']?=?pd.to_datetime(equity['交易日期'])
????draw_equity_curve(equity,?time='交易日期',?data_dict={choose_name[self.fund_num]:?'equity_curve',?'中國(guó)基金加權(quán)總指數(shù)':?'benchmark'},
??????????????????????save_path=save_path)
3.5 評(píng)價(jià)指標(biāo)
對(duì)于基金的收益,僅僅看收益率曲線,獲得的評(píng)價(jià)較為主觀,要客觀比較收益的好壞,還要借助指標(biāo),比如:年化收益率、最大回撤等,更加復(fù)雜的還有夏普比率、Jensen指數(shù)等,需要的評(píng)價(jià)指標(biāo)加入一下自定義的函數(shù)strategy_evaluate()即可。
#?計(jì)算策略評(píng)價(jià)指標(biāo)
def?strategy_evaluate(equity):
????#?===新建一個(gè)dataframe保存回測(cè)指標(biāo)
????results?=?pd.DataFrame()
????#?===計(jì)算累積凈值
????results.loc[0,?'累積凈值']?=?round(equity['equity_curve'].iloc[-1],?2)
????#?===計(jì)算年化收益
????annual_return?=?(equity['equity_curve'].iloc[-1])?**?(
????????????'1?days?00:00:00'?/?(equity['交易日期'].iloc[-1]?-?equity['交易日期'].iloc[0])?*?365)?-?1
????results.loc[0,?'年化收益']?=?str(round(annual_return?*?100,?2))?+?'%'
????#?計(jì)算當(dāng)日之前的資金曲線的最高點(diǎn)
????equity['max2here']?=?equity['equity_curve'].expanding().max()
????#?計(jì)算到歷史最高值到當(dāng)日的跌幅,drowdwon
????equity['dd2here']?=?equity['equity_curve']?/?equity['max2here']?-?1
????#?計(jì)算最大回撤,以及最大回撤結(jié)束時(shí)間
????end_date,?max_draw_down?=?tuple(equity.sort_values(by=['dd2here']).iloc[0][['交易日期',?'dd2here']])
????#?計(jì)算最大回撤開(kāi)始時(shí)間
????start_date?=?equity[equity['交易日期']?<=?end_date].sort_values(by='equity_curve',?ascending=False).iloc[0]['交易日期']
????#?將無(wú)關(guān)的變量刪除
????equity.drop(['max2here',?'dd2here'],?axis=1,?inplace=True)
????results.loc[0,?'最大回撤']?=?format(max_draw_down,?'.2%')
????results.loc[0,?'最大回撤開(kāi)始時(shí)間']?=?str(start_date)
????results.loc[0,?'最大回撤結(jié)束時(shí)間']?=?str(end_date)
????return?results.T
在主程序中調(diào)用業(yè)績(jī)?cè)u(píng)價(jià)函數(shù)
def?evaluate(self):
????equity?=?self.yield_curve()
????choose_name?=?self.get_col_name()
????#?===計(jì)算策略評(píng)價(jià)指標(biāo)
????rtn_data=?strategy_evaluate(equity)
????rtn_data.rename(columns={0:?choose_name[self.fund_num]},?inplace=True)
????#print(rtn_data)
????return?rtn_data
3.6運(yùn)行程序
在運(yùn)行程序前,要在目錄里面建好保存收益率曲線和收益率評(píng)價(jià)的文件夾,如圖所示:
在company_name處輸入基金公司的簡(jiǎn)稱,num即返回基金公司下的基金數(shù)量,程序自動(dòng)遍歷其旗下基金,并將其收益曲線圖存入chart,將其收益評(píng)價(jià)指標(biāo)合并后存入sheet。
if?__name__?==?'__main__':
????company_name?=?'光大'
????analyse?=?Analyse(company=company_name,?fund_num?=?0)
????num?=?analyse.col_num()
????df_ret_analyse?=?pd.DataFrame()
????for?i?in?range(num):
????????name?=?analyse.get_col_name()[i]
????????try:
????????????analyse?=?Analyse(company=company_name,?fund_num=i)
????????????analyse.draw_curve(save_path?=?root_path?+?r'/result\chart/%s.jpg'?%?name)
????????????df?=?analyse.evaluate()
????????????df_ret_analyse?=?pd.concat([df_ret_analyse,?df],?axis=1)
????????except:
????????????print(name?+?'數(shù)據(jù)錯(cuò)誤')
????print(df_ret_analyse)
????df_ret_analyse.to_csv(root_path?+?f'/result\sheet\{company_name}retns.csv')
4、運(yùn)行效果展示
在company_name處輸入'光大',查看光大資管的公募基金收益情況。運(yùn)行后,收益率曲線已經(jīng)全部保存至/result/chart
點(diǎn)開(kāi)其中一個(gè)查看:

收益指標(biāo)則保存到了/result/sheet下:

5、完整代碼
5.1 函數(shù)
import?os
import?matplotlib.pyplot?as?plt
import?pandas?as?pd
import?numpy?as?np
#?===獲取項(xiàng)目根目錄
_?=?os.path.abspath(os.path.dirname(__file__))??#?返回當(dāng)前文件路徑
root_path?=?os.path.abspath(os.path.join(_,?'..'))??#?返回根目錄文件夾
#?===繪制收益率曲線
#?繪制策略曲線
def?draw_equity_curve(df,?data_dict,?time=None,?pic_size=[16,?9],?dpi=72,?font_size=25,?save_path='fig.jpg'):
????plt.rcParams['font.sans-serif']?=?['SimHei']
????plt.rcParams['axes.unicode_minus']?=?False
????#?plt.style.use('dark_background')
????plt.figure(figsize=(pic_size[0],?pic_size[1]),?dpi=dpi)
????plt.xticks(fontsize=font_size)
????plt.yticks(fontsize=font_size)
????for?key?in?data_dict:
????????if?time:
????????????plt.plot(df[time],?df[data_dict[key]],?label=key)
????????else:
????????????plt.plot(df.index,?df[data_dict[key]],?label=key)
????plt.legend(fontsize=font_size)
????plt.grid()
????#plt.show(
????plt.savefig(save_path)
#?計(jì)算策略評(píng)價(jià)指標(biāo)
def?strategy_evaluate(equity):
????#?===新建一個(gè)dataframe保存回測(cè)指標(biāo)
????results?=?pd.DataFrame()
????#?===計(jì)算累積凈值
????results.loc[0,?'累積凈值']?=?round(equity['equity_curve'].iloc[-1],?2)
????#?===計(jì)算年化收益
????annual_return?=?(equity['equity_curve'].iloc[-1])?**?(
????????????'1?days?00:00:00'?/?(equity['交易日期'].iloc[-1]?-?equity['交易日期'].iloc[0])?*?365)?-?1
????results.loc[0,?'年化收益']?=?str(round(annual_return?*?100,?2))?+?'%'
????#?計(jì)算當(dāng)日之前的資金曲線的最高點(diǎn)
????equity['max2here']?=?equity['equity_curve'].expanding().max()
????#?計(jì)算到歷史最高值到當(dāng)日的跌幅,drowdwon
????equity['dd2here']?=?equity['equity_curve']?/?equity['max2here']?-?1
????#?計(jì)算最大回撤,以及最大回撤結(jié)束時(shí)間
????end_date,?max_draw_down?=?tuple(equity.sort_values(by=['dd2here']).iloc[0][['交易日期',?'dd2here']])
????#?計(jì)算最大回撤開(kāi)始時(shí)間
????start_date?=?equity[equity['交易日期']?<=?end_date].sort_values(by='equity_curve',?ascending=False).iloc[0]['交易日期']
????#?將無(wú)關(guān)的變量刪除
????equity.drop(['max2here',?'dd2here'],?axis=1,?inplace=True)
????results.loc[0,?'最大回撤']?=?format(max_draw_down,?'.2%')
????results.loc[0,?'最大回撤開(kāi)始時(shí)間']?=?str(start_date)
????results.loc[0,?'最大回撤結(jié)束時(shí)間']?=?str(end_date)
????return?results.T
5.2主程序
import?pandas?as?pd
import?re
from?基金收益分析.func.myfunc?import?*
class?Analyse(object):
????def?__init__(self,?company='海通',?fund_num?=?0):
????????self.company?=?company
????????self.fund_num?=?fund_num
????????pass
????def?day_data(self):
????????day_data?=?pd.read_csv(root_path+'/data/大集合轉(zhuǎn)公募基金復(fù)權(quán)凈值day.CSV',?encoding='gbk')
????????day_data.rename(columns={'Unnamed:?0':?'交易日期'},?inplace=True)
????????day_data['交易日期']?=?pd.to_datetime(day_data['交易日期'])
????????#day_rtn.set_index('交易日期',?inplace=True)
????????day_data.dropna(axis=1,?how='any',?inplace=True)
????????return?day_data
????def?index_day_data(self):
????????index_day_rtn?=?pd.read_csv(root_path+'/data/中國(guó)基金加權(quán)指數(shù)day.csv',
?????????????????????????????????????encoding='gbk')
????????index_day_rtn.rename(columns={'Unnamed:?0':?'交易日期'},?inplace=True)
????????index_day_rtn['交易日期']?=?pd.to_datetime(index_day_rtn['交易日期'])
????????index_day_rtn['中國(guó)基金加權(quán)總指數(shù)']?=?index_day_rtn['中國(guó)基金加權(quán)總指數(shù)'].pct_change()
????????return?index_day_rtn
????def?get_col_name(self):
????????day_data?=?self.day_data()
????????#?匹配正則表達(dá)式
????????pattern?=?re.compile('^%s'%?self.company)
????????col_name?=?day_data.columns.tolist()
????????choose_name?=?[]
????????for?name?in?col_name:
????????????if?pattern.match(name):
????????????????choose_name.append(name)
????????print(choose_name)
????????return?choose_name
????def?col_num(self):
????????choose_name?=?self.get_col_name()
????????num?=?len(choose_name)
????????#?print(num)
????????return?num
????def?choose_data(self):
????????choose_name?=?self.get_col_name()
????????day_data?=?self.day_data()
????????day_data?=?day_data[['交易日期',?choose_name[self.fund_num]]]
????????day_data[choose_name[self.fund_num]]?=?day_data[choose_name[self.fund_num]].pct_change()
????????day_rtn?=?day_data
????????#?和指數(shù)信息合并
????????index_day_rtn?=?self.index_day_data()
????????equity_day?=?pd.merge(day_rtn,?index_day_rtn,?on='交易日期',?how='left'?)
????????equity_day.dropna(axis=0,?how='any',?inplace=True)
????????equity_day.rename(columns={choose_name[self.fund_num]:?'漲跌幅',?'中國(guó)基金加權(quán)總指數(shù)':?'指數(shù)漲跌幅'},?inplace=True)
????????return?equity_day
????def?yield_curve(self):
????????equity?=?self.choose_data()
????????equity['equity_curve']?=?(equity['漲跌幅']?+?1).cumprod()
????????equity['benchmark']?=?(equity['指數(shù)漲跌幅']?+?1).cumprod()
????????equity?=?equity[['交易日期',?'equity_curve',?'benchmark',?'漲跌幅',?'指數(shù)漲跌幅']]
????????return?equity
????def?draw_curve(self,?save_path?=?r'fig.jpg'):
????????choose_name?=?self.get_col_name()
????????equity?=?self.yield_curve()
????????equity?=?equity.reset_index(drop=True)
????????equity['交易日期']?=?pd.to_datetime(equity['交易日期'])
????????draw_equity_curve(equity,?time='交易日期',?data_dict={choose_name[self.fund_num]:?'equity_curve',?'中國(guó)基金加權(quán)總指數(shù)':?'benchmark'},
??????????????????????????save_path=save_path)
????def?evaluate(self):
????????equity?=?self.yield_curve()
????????choose_name?=?self.get_col_name()
????????#?===計(jì)算策略評(píng)價(jià)指標(biāo)
????????rtn_data=?strategy_evaluate(equity)
????????rtn_data.rename(columns={0:?choose_name[self.fund_num]},?inplace=True)
????????#print(rtn_data)
????????return?rtn_data
if?__name__?==?'__main__':
????company_name?=?'光大'
????analyse?=?Analyse(company=company_name,?fund_num?=?0)
????num?=?analyse.col_num()
????df_ret_analyse?=?pd.DataFrame()
????for?i?in?range(num):
????????name?=?analyse.get_col_name()[i]
????????try:
????????????analyse?=?Analyse(company=company_name,?fund_num=i)
????????????analyse.draw_curve(save_path?=?root_path?+?r'/result\chart/%s.jpg'?%?name)
????????????df?=?analyse.evaluate()
????????????df_ret_analyse?=?pd.concat([df_ret_analyse,?df],?axis=1)
????????except:
????????????print(name?+?'數(shù)據(jù)錯(cuò)誤')
????print(df_ret_analyse)
????df_ret_analyse.to_csv(root_path?+?f'/result\sheet\{company_name}retns.csv')
6、結(jié)語(yǔ)
此處的分析作為分析的一個(gè)框架,可以添加更多的有趣的研究?jī)?nèi)容,比如:對(duì)基金的收益數(shù)據(jù)可以計(jì)算其期望,方差,峰度,偏度等指標(biāo),對(duì)收益率也可以計(jì)算夏普比率等指標(biāo)??梢愿由钊氲姆治龌鸬氖找娴淖儎?dòng)。
最后強(qiáng)烈推薦帥帥老師的python課程,科班程序員出生的帥帥老師,代碼寫(xiě)的非常規(guī)范,初學(xué)者可以少走彎路。并且?guī)泿浝蠋煹拇鹨煞浅<皶r(shí),仔細(xì)回復(fù)每個(gè)問(wèn)題,筆者每次遇到網(wǎng)上查詢不到答案的問(wèn)題時(shí),都會(huì)馬上請(qǐng)教帥帥老師,節(jié)省了許多自己debug的時(shí)間!
總之,無(wú)論是初學(xué)者從頭學(xué)習(xí),還是老手查漏補(bǔ)缺,找一位專家答疑,帥帥的課程都物超所值。
