用【马科维茨资产组合选择模型】来咕叽咕叽

        咕叽咕叽估基估基的谐音梗。

        这里想说的就是【评估基金绩效】。基金绩效评估对机构型投资者应该是有相当成熟的操作体系和框架的,但是对于个人而言,面对浩如烟海的基金池,要怎样对所持基金进行评估确实是一件棘手的事情。而如果没有对所持基金进行合适的评估,则很难知道自己好不容易申购入手的基金到底好不好。会不会被基金经理卖了,还在帮他数钱。你以为你投的稳健精选,真的就那么稳健?优势制造究竟都是哪些行业?基金经理在年内进出各种股票、债券标的之后,这只基金的风险究竟处于什么水平?你手里的基金真的具备合意性价比,值得你把闲置或是不知从哪儿贷来的资金放在这只鸡身上?

        前言:我们身边究竟有多少只基?

        基金按资金募集方式可以分为【公募】和【私募基金】,私募基金通过非公开方式募集,募集的对象是少数特定的投资者。对广大社会公众来说,更容易接触选择和投资的对象,自然就是公募基金了。        

        2022年二季度,国内的公募基金产品首次突破一万,截至10月,公募基金总数10,263只,基金规模26.59万亿
        截至2022年12月13号,深市上市股票数量为2734家,流通市值27万亿(273,396.20亿);沪市则为2204家,流通市值40万亿(407,547.42亿)。
        公募基金数量2倍于股票数量,茫茫基海要选择适合自己的基金,想想都头皮发麻。 

b116e1f9410144878620f0c71e40fdfa.png

        从下图可以看到,私募基金的产品数量更多(133,775只),但是他们大多面向经验或者资金都比我们普罗大众强胜不少的个人或者机构,我们了解一下就好,暂时不用替他们头皮发麻。

51935ad5dbc841fa85b0b15a41c5db49.png

        原本我是没有写这篇文章的打算。凑巧在练手聚宽的CAPM(资本资产定价模型)策略时,想起其实CAPM这系列模型(全协方差模型:马科维茨模型,非全协方差模型:指数模型以及CAPM模型)是可以用在各种投资标的,像货币对组合、股票组合、债券组合,或者是基金组合(类似于FOF)。讲道理,用来估基应该也是挺好玩的。

        基金这款标的,身边总有不少朋友不时会聊起,是不少家庭进行理财比较常用的选择。但具体怎么选,估计会有不少筒子也是小懵。要么凑巧看到电梯广告,要么把朋友介绍的小道消息稍作综合,从里面挑挑拣拣然后一拍大腿敲定购买的对象。稍微愿意花点儿时间的,可能会去天天基金、雪球(蛋卷)基金看看大咖们的带货,或者参考里面的指数估值板块。但是并没有对这些指数估值的准确性如何进行验证。很明显,我们一般来说是没有太多专业知识或者工具,能辅助筛选。既然我刚好练手资产组合策略的构建,借此机会,就用基金这款标的来练练手,看看能不能通过马科维茨这款经典全协方差资产组合模型,帮助我们咕叽估基

        本练手手稿聚宽数据读取部分的代码主要参考文章如下:

用python来分析一波基金数据_迢迢x的博客-CSDN博客_python基金分析导语:哈喽,哈喽~马上又要到年底了,所有的人都在冲业绩大捞一笔,然后准备回家过年。这里小编想问一下,你们的基金经理业绩还好吗????今天小编通过量化投资平台和Python来分析一波基金数据。正文:本次分析用的是聚宽(https://www.joinquant.com/)平台,新用户注册后会有6个月试用期,期间可以免费使用平台所有数据,每天可调用100万条数据,完全够我们分析了。注册账号后,我们就可以调用聚宽的数据。以下代码我用 Python 3.8+jupyter 编写、运行。https://blog.csdn.net/a55656aq/article/details/122103777

本文框架:

37ec4f27b4664640921894a454375930.png

1.安装数据接口,导入相关库类

        本次练手实在不想写爬虫抓取基金数据,看了参考的文章后,我就去申请了聚宽Python版API的免费试用。据说以前可以试用6个月,而现在的免费试用期限只有3个月。
        申请成功之后,小伙伴们可以pip install jqdatasdk来安装聚宽数据接口。需要注意的是,该接口某些基金查询语句,只有在调用触发(像获取基金净值时的:get_extras)的时候才给你打开14天的试用权限,这14天可得好好珍惜,尽量保存pickle版本到本地,方便以后重复读取分析。

af6401a896e94d939dd3e789877a9a0c.png          安装好本地接口之后,就可以开始导入库,输入你的聚宽账户名密码,登录API接口。

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.optimize import minimize
import warnings
warnings.filterwarnings('ignore')           #后续会出现一些警告信息,嫌烦,过滤掉

from jqdatasdk import *
auth("你的聚宽登录用户名","你的聚宽登录密码")   #登录聚宽的数据接口

2.数据下载、整理

          下载所有基金代码、名称等基本信息(下载后的具体字段如下图),把代码字段(其实就是df.index)的后缀处理掉,方便后续查询持股、持股占比信息。

6044804ee2a94f9a9ef1eaa446c205ff.png

df=get_all_securities(['fund','open_fund'],'2022-12-10')     #'2022-12-10'这个语句不加的话,包含已退市的基金。加了该语句,则代表当天仍在交易的基金
df.index=[x.split(".")[0] for x in df.index]                 #由于JointQuant的基金组合查询query,只接收没有后缀的数字代码,所以这里需要把逗点及后缀去除
df.to_pickle("JointQuant_Fund_data.pkl")                     #需要保存时再释放注释
code_lst=list(set(df.index))
#下列代码是统计去除后缀后,哪些代码是重复的。例如:159601.OF,159601.XSHE,一个是场外交易的基金,一个是场内交易的基金,后缀不一样,但其实是同一只基金。
dupli_dict={}
for code in code_lst:
    if codes.count(code)>1:
        dupli_dict[code]=codes.count(code)
print(f"总共有{len(dupli_dict)}只基金数字代码重复")

        通过上述代码可以了解到有不少基金产品(此处为586只)其实是通过后缀区分场内和场外,但基金净值应该是同样的。同时我们看到下载的基金总数为16914只,就算你是勤劳的小蜜蜂一天研究10只,也差不多得4年半才能摸清楚这一万多只基金的特性。接下来,我们假定你是一个乐於冒险的人,有着较强的风险偏好,那我们筛选一下这里面的偏股型基金,先把持股占比超过50%的基金筛到一个篮子里。
        参考聚宽的API文档,编写query函数如下,左边的函数能帮我们查询基金里面各细分金融资产持有百分比相关信息的,右边的函数则可以帮助我们查询进入篮子的偏股型基金持有的前10只股票信息。写好query函数之后,扔给finance.run_query()来执行该查询。如参考的文章所提示,由于聚宽的数据接口一次只返回5000行,因此需要通过循环来限制每次查询的数量<=5000。


query(finance.FUND_PORTFOLIO)                    query(finance.FUND_PORTFOLIO_STOCK)
  可通过该语句查询下列字段的信息                                       可通过该语句查询下列字段的信息
主要与组合各细分类别占比有关                                                 主要与持有的具体股票有关


44f415950cb34febaa70917070b8ef05.png7b660ba9b92d4e84bd6b0d9e47956c31.png

#获取基金股票投资占比数据
#定义股票投资占比查询函数
def asset_pct_query(code_lst):
    q=query(finance.FUND_PORTFOLIO.code,                #拿到基金代码
                  finance.FUND_PORTFOLIO.name,          #拿到基金名字
                  finance.FUND_PORTFOLIO.period_end,    #返回定期报告当期的最后一天日期
                  finance.FUND_PORTFOLIO.report_type,   #设定数据定期报告的频次(按年月日等)
                  finance.FUND_PORTFOLIO.stock_rate     #基金股票投资的百分比
           ).filter(finance.FUND_PORTFOLIO.code.in_(code_lst),finance.FUND_PORTFOLIO.period_end.in_(["2022-03-31","2022-06-30","2022-09-30"]),
                   finance.FUND_PORTFOLIO.report_type.in_(['第一季度','第二季度','第三季度']))
    return q

#对目标股票清单分批进行股票投资占比查询
i=0
while i<len(code_lst):
    tmp_lst=code_lst[i:i+1500]       #每隔1500只基金截取一段代码列表
    q=asset_pct_query(tmp_lst)
    tmp_df=finance.run_query(q)       #聚宽自带的查询函数
    if i==0:
        asset_pct_df=tmp_df
    else:
        asset_pct_df=pd.concat([asset_pct_df,tmp_df])
    i+=1500
asset_pct_df.to_pickle("JointQuant_Fund_Stock_Rate.pkl")

stock_fund_df=asset_pct_df[asset_pct_df['stock_rate']>50]  #筛选持股占比超过50%的基金
stock_fund_code_lst=list(set(stock_fund_df['code']))       #生成持股占比超50%基金代码列表



#获取基金前十持仓股票信息
#定义前十持仓股票投资查询函数
def stock_holding_query(lst):
    q=query(finance.FUND_PORTFOLIO_STOCK                         #获取基金持有的具体股票
           ).filter(finance.FUND_PORTFOLIO_STOCK.code.in_(lst),  #股票须在设定清单内
                    finance.FUND_PORTFOLIO_STOCK.rank<=10,       #只需要前10只股票
                    finance.FUND_PORTFOLIO_STOCK.period_end.in_(["2022-03-31","2022-06-30","2022-09-30"]),
                    finance.FUND_PORTFOLIO_STOCK.report_type.in_(['第一季度','第二季度','第三季度']))                                                  #一二三季度季报数据
    return q

#对目标股票清单分批进行十大持仓股票清单查询
i=0
while i<len(stock_fund_code_lst):
    print("获取基金持仓:"+str(i))
    tmp_lst=stock_fund_code_lst[i:i+150]
    q=stock_holding_query(tmp_lst)
    tmp_df=finance.run_query(q)
    if i==0:
        stock_holding_df=tmp_df
    else:
        stock_holding_df=pd.concat([stock_holding_df,tmp_df])
    i+=150

stock_holding_df.to_pickle("JointQuant_fund_stock_holding.pkl")

        具体数据查询的代码如上,筛选后的基金产品为7371。把一万七千只基金产品,缩窄到七千只,虽然对选择困难症的筒子来说还是头疼,但好歹迈出了第一步,还是值得庆贺的。

d4bb406f0cfa48f692f04f6a36fd13c1.png

faa1ecdcd0ba4a64b2d9eca99b4be5b3.png        到目前为止我们里面包含的信息还是比较原始,接下来我们把股票对应的行业属性、板块概念属性的信息添上去,具体代码放在下面。下面是一些小贴士,请注意查收
        1) 尽量减少inquiry发起的次数。毕竟聚宽的免费接口,一天只有50万的数据下载额度,说多不多。inquiry多发几次,这额度还真的不怎么够用。所以非常建议把数据落到本地,后续调用数据的时候可以直接读取本地文档;
        2)保存本地数据,建议用pickle腌制版,这样像行业属性这种数据框,不少列里面也是字典。如果用pickle腌制起来,后续调用和处理起来相当方便,无需再通过json把csv里面的字符串重新解码成字典。这点很重要!

#获取所有股票代码,查询每只股票的行业属性,把结果保存到本地。
stock_code_df=get_all_securities(types=['stock'])
code_to_industry_lst=list(stock_code_df.index)
code_to_industry_result= get_industry(security=code_to_industry_lst, date="2022-12-01")
code_to_industry_result=pd.concat({k:pd.DataFrame(v) for k ,v in code_to_industry_result.items()})  #先把代码--行业属性组装成字典,然后再拼合成数据框
code_to_industry_result.to_pickle("All_Stock_to_Industry_info.pkl")       

#保存完之后,后续使用时,最好直接读取本地的腌制好的pickle
all_stock_lst=pd.read_pickle("All_Stock_to_Industry_info.pkl")
all_stock_lst=list(all_stock_lst.index.levels[0])

#用本地csv里面的股票列表,查询聚宽,并返回该股票的所属概念
all_stock_to_concept_result=get_concept(security=all_stock_lst, date="2022-12-01")
all_stock_to_concept_df=pd.DataFrame.from_dict(all_stock_to_concept_result).T
all_stock_to_concept_df.to_pickle("All_stock_to_concept_result.pkl")
print("all done")

        读取pickle腌制好的基金数据(包含股票投资百分比信息),筛选股票投资比例>20%的基金stock_fund_df
        读取所有基金前十持股信息stock_holding_df

        用merge函数,从所有基金持股数据池(stock_holding_df)内,筛选出stock_fund_df指定的股票投资比例>20%基金的前十持股信息filter_stock_holding_df

        读取本地pickle腌制好的股票--行业属性数据,本文读入申万第一重【sw_l1】的行业分类标准,然后再把【股票代码__行业属性】拼合成字典集code_to_industry_dict
        给filter_stock_holding_df添加【industry_name】列,通过apply方法为每一行股票从code_to_industry_dict内匹配对应的行业属性;

        读取本地pickle最开始腌制好的基金数据,把【基金代码__基金种类】拼合成字典集fund_type_dict
        给filter_stock_holding_df添加【fund_type】列,通过apply方法为每一行股票从fund_type_dict内匹配对应的行业属性;

local_asset_pct_df=pd.read_pickle("JointQuant_Fund_Stock_Rate.pkl").set_index(['code','report_type'])                                                         #读取本地数据,
# stock_fund_df=local_asset_pct_df[local_asset_pct_df['stock_rate']>20]   #获取持股占比超20%的基金df,本例把所有比例的基金都读进来,方便后续做数据透视表进行分析                                  
stock_fund_df=local_asset_pct_df.copy()                                     #读取本地数据,获得持股占比超50%的基金df
#************************************************************************************************************
stock_holding_df=pd.read_pickle("JointQuant_fund_stock_holding(total).pkl").set_index(['code','report_type'])     #读取本地数据,获取十大持股信息df
#************************************************************************************************************
code_to_industry_df=pd.read_pickle("All_Stock_to_Industry_info.pkl")                                      #读取本地数据,获取股票代码--行业属性
code_to_industry_df=code_to_industry_df.unstack()['sw_l1']['industry_name']
code_to_industry_df.index=[x.split(".")[0] for x in code_to_industry_df.index]
code_to_industry_dict=dict(zip(code_to_industry_df.index,code_to_industry_df))
#************************************************************************************************************
fund_type_df=pd.read_pickle("JointQuant_Fund_data.pkl")                                                  #读取本地数据,获取基金分类属性      
fund_type_dict=dict(zip(fund_type_df.index,fund_type_df['type']))
#************************************************************************************************************
filter_stock_holding_df=stock_fund_df.merge(stock_holding_df,how='inner',on=['code','report_type'])      #根据设定持股百分比清单,拼合持股信息数据

filter_stock_holding_df=filter_stock_holding_df.assign(industry_name="",fund_type="")                   #给拼合后的数据框,添加【行业名】、【基金种类】列
filter_stock_holding_df['industry_name']=filter_stock_holding_df['symbol'].apply(lambda x:code_to_industry_dict.get(x))       #根据所持每个股票代码,从代码_行业属性字典内匹配信息
filter_stock_holding_df.reset_index(inplace=True)
filter_stock_holding_df['fund_type']=filter_stock_holding_df['code'].apply(lambda x:fund_type_dict.get(x))                   #根据所持每个基金,从基金_基金种类字典内匹配信息
filter_stock_holding_df.to_pickle("JointQuant_Fund_StockRate_with_type_sw_l1.pkl")

3.基金数据概览

3.1 不同类型基金的持股意愿   

        我们先稍微统计一下不同股票持股比例的基金产品数量、基金总数的占比(代码如下)。

stock_rate_df=pd.read_pickle("JointQuant_Fund_StockRate_with_type_sw_l1.pkl")
total_num_of_fund=stock_rate_df[stock_rate_df['report_type']=='第三季度']['code'].drop_duplicates().count()

result_df=pd.DataFrame()
for pct in range(20,100,10):
    tmp_df=stock_rate_df[stock_rate_df['stock_rate']>pct]
    stock_holding_stats=pd.pivot_table(tmp_df,index=['fund_type'],values=['code'],aggfunc=lambda x: len(x.unique())) 
    result_df=result_df.merge(stock_holding_stats,how='outer',left_index=True,right_index=True)
result_df.columns=[f"{x}%Stck" for x in range(20,100,10)]
display(result_df)
display(((result_df/total_num_of_fund).mul(100)).round(1).astype(str).add("%"))

8f3beefe9f61425a860f4aecf23cfd72.png

         由上图可以看到,2022年第三季度时,除了ETF型基金(ETF基本是90%的持仓比例,不过ETF基金总数较少,占比公募基金总数6%左右),大部分类型的公募基金都把股票投资的仓位控制在相对低的水平。大部分的混合型公募基金把股票持仓控制80%,只有四成的混合型基金持仓超过90%。而七成的股票型公募基金则持仓超90%。

3.2  基金买股票的行业偏好 

        接下来,我们参照参考文章的做法,把所有基金在3个季度持有的股票映射到该股票所属的行业内,从而得出所有基金在股票建仓时,对行业的偏好是怎样的。由于此前我们已经建立好了基金前十持仓股票对行业的映射表,且腌制了一份Pickle数据。下面直接读取该数据,并通过pandas的数据透视表来统计之后,画出统计结果的柱状图。代码如下:

#用数据透视表统计第三季度(自定义)基金所有前十持仓股票的行业属性,观察哪个行业最受基金欢迎
stock_holding_df=pd.read_pickle("JointQuant_Fund_StockRate_with_type_sw_l1.pkl")
industry_count_df=stock_holding_df.pivot_table(index='industry_name',columns=
                                           ['report_type'],values='code',
                                           aggfunc=lambda x:len(x.unique()))
cols_name=['第一季度','第二季度','第三季度']
industry_count_df=industry_count_df[cols_name]
industry_count_df.dropna(inplace=True)             
industry_count_df.sort_values(by='第三季度',inplace=True)    #根据第三季度的数据,对柱图进行排序
plt.rcParams['figure.figsize']=(12,8)
plt.rcParams['font.sans-serif'] = ['SimHei']  #需要通过该设定,启用图表的中文显示
industry_count_df.plot.bar()
plt.xticks(rotation=70)

        由下面的柱图我们可以看到,最受机构投资者欢迎的行业:电气设备,然后是食品,接下来就是电子、医药生物。第二季度甚至有接近6000只基金在这个行业内持有仓位(具体持仓份额的数据我们其实也可以统计出来。这里就不作展示了)。第三季度热度突然拔高的主要有:【国防军工】、【机械设备】、【煤炭】、【公用事业】。

550279e3a7ef44ff8f25c5265f8976c7.png

3.3 你钟意的股票都有哪些基金在采购

        有了此前下载下来的数据,我们可以用来定制一些个性化的数据筛选功能。例如这个高频场景:你近期关注了某几只股票,在各种公众号、网站上也看了不少相关文章,觉得这些股票前景不错。于是想了解一下市场上的机构投资者是否跟你有同感,会不会他们已经买了不少等着割你的韭菜,还是他们年内对这某几只股票稳定持有。如果是稳定持有的话,或许你可以考虑通过持有这些基金份额,从而间接持有你感兴趣的股票。毕竟稳定持有该类股票的基金,投研能力还是会比个人要高一些,他们的进出会理论上应该会比个人要智慧一点。如果由他们来代持,对于时间精力都不太充裕的个人投资者,或许会是不错的选择。原文是用了给原数据框着色的功能来实现这一筛选功能,我这里就通过数据透视表来进行筛选。

#此处准备根据关注的股票来筛选基金,例如我想看一下主要增持【'贵州茅台','宁德时代','腾讯控股有限公司','中航光电'】的基金总数有多少,增持的份额总数是多少,平均持有金额是多少
def filtered_fund_with_favor_stock(df,favor_stock_lst,quarter):
    tmp_df=df[df.report_type==quarter].copy()
    tmp_df.set_index(['code','report_type'],inplace=True)
    favor_stock_df=tmp_df[tmp_df['name_y'].isin(favor_stock_lst)]
    favor_stock_df.sort_values(by=['name_y','market_cap'],ascending=False,inplace=True)
    favor_stock_df['market_cap']=favor_stock_df['market_cap'].apply(lambda x: (x/100000000))
    return favor_stock_df

favor_stock_lst=['贵州茅台','宁德时代','腾讯控股有限公司','中航光电']
filtered_fund_df=filtered_fund_with_favor_stock(stock_rate_df,favor_stock_lst,quarter='第三季度')
display(filtered_fund_df.head())

#####################################################################################################################
filtered_fund_result=pd.DataFrame()
quarter_lst=['第一季度','第二季度','第三季度']
for quarter in quarter_lst:
    filtered_fund_df=filtered_fund_with_favor_stock(stock_rate_df,favor_stock_lst,quarter)
    filtered_fund_stats=pd.pivot_table(filtered_fund_df,index=['name_y'],values=['market_cap'],aggfunc=[np.sum,np.mean,len]) 
    filtered_fund_result=filtered_fund_result.merge(filtered_fund_stats,how='outer',left_index=True,right_index=True)    
#####################################################################################################################
display(filtered_fund_result)

         我们可以得到每只股票的具体持有基金的名称,持有市值,且该表已经按照持有市值进行排序:

      

         用数据透视表对数据进行进一步的归总,结果如下表。从下表我们可以看到【宁德时代】公募基金在第三季度集中减持,减持金额为590亿元。而中航光电年内则主要处于被增持的通道,持有机构的数量也有所增加。

03bddaf1ec654c4c9da97d1d8ae21ba6.png

         很明显,在python里面,非常容易定制属于你的个性化基金概览。挑选基金变得相对高效不少。        

4.给自己的基金组合来一次【咕叽咕叽】

4.1 基金的“性价比”

        信奉分散化的个人投资者,一般都会持有若干只基金。不过基金类型可以随意,每只基金投资的比例也是随意设定。至于该基金投资的行业,可能是自己在某公众号看到过、或者新闻里听到过的某个拥有着朝阳前景的行业。

        我们日常购买的商品,会用性(能)价(格)比来判断同一款产品,是否值得买: 同等价格,提供最高的性能;或者同等性能,以最低的价格售出。具备高性价比的产品,自然会让消费者趋之若鹜。对于性价比不高的产品,我们消费者可以很轻松的用脚投票,不买就是。

        用脚投票这一策略能否轻松的适用于基金的选购?

        目前,我们可以看到不少平台的基金申购费用其实已经很低,大体小于0.1%。而运作费率(由管理费、托管费、销售服务费等组成)则看基金的管理类型有高有低,高的有1.5%,低的为0.3%等等。 最后就是赎回费,持有期<7天是1.5%,持有期7天< 且<180天,有些直接调为0.1%。因此如果频繁买卖一只基金,个人投资者可能面临的总体费率区间为0.5%---3.0%。由此看来,个人所持的基金组合适合中低频次的调仓。频繁的用脚投票,会对投资者的年化收益造成不小的侵蚀。

        但和股票仓位调整一样,基金的仓位调整还是相当必要的。当你把钱委托给一位基金经理,而他/她把你的资金给打理的一团糟,你如果还不通过用脚投票来表达你对糟糕投资结果的愤怒,适时止损以避免遭受更高的损失,那这笔钱还不如直接送给这位基金经理。因此,哪怕面临较高的用脚投票成本,该有的调仓还是得有的。       

        对于基金或者其他金融标的,如果该组合只考虑风险资产包(不考虑资金在【无风险资产包】、【风险资产包】的分配),我们用的性价比指标是收益(价)风险(性)比。如果考虑了风险资产无风险资产的组合,则性价比指标就是我们所熟知的Sharp Ratio (夏普比率)----(风险溢价/风险溢价的标准差)。所谓风险溢价,就是你的风险资产包,超出无风险利率的收益。

4.2 Yours基金组合的最优投资比例

        现如今,大部分的个人投资者大多都懂分散化投资这一分散风险的理念。攥着钱进入股票市场或者购买基金、理财产品的时候,还是能念叨一下那句投资界的白话名言:【别把鸡蛋都放到一个篮子里】。

        但是问题来了:如果你有10万本金,你想买几只基金,怎么样才算是分散化?把10万本金,买入10多只主要投资于国防军工主题的基金,这种让你看起来拥有了十多只基金的做法,并不能真正让你实现分散化。

        一般来说,只要你的投资组合内的资产之间的相关系数小于1,就已经天然拥有了分散化的能力。当然,相关系数越小分散效果越好,甚至小到趋近于-1的时候,你就拥有了无风险(波动率为零),且收益率被完美锁定的组合。

        实际生活中,相关系数为-1的两只基金基本不可能找到。如何找到相关系数更小的组合这个任务,我们先放一边。假设你手里能申购的基金就那么几只,这个假设合理的原因在于,忙于搬砖的你,如果你身边的电梯广告从年头到年尾,就贴了那几只基金的广告。那你的脑海里想起要买的基金很可能就那么几只~~  那我们来看看给定可投资的基金,如何调整你在不同基金的投资比例,才能更好的优化基金组合的性价比。

4.3 马科维茨模型的估基实战

        我们先用马科维茨模型来看看自己选择的基金投资包,风险收益比率是否最优。从此前的基金总表中根据个人偏好,稍显随意的挑出4只,组成我们的基金投资包。各自权重按照25%来计算---【'000176','001563','005250','014829'】----【嘉实沪深300指数研究增强型证券投资基金A类华富健康文娱灵活配置混合型证券投资基金银华估值优势混合型证券投资基金诺德新能源汽车混合型证券投资基金A类】。

#读取所有基金DF,利用里面的代码列作为搜寻基准,把my_lst的基金代码转化为完整的可搜寻的聚宽代码标准
fund_code_df=pd.read_pickle("JointQuant_Fund_Code_data.pkl")
# my_lst=['000729','004812','011506']
my_lst=['010843','510590','004721','012298']
result_security_lst=[fund_code_df[fund_code_df.index.str.contains(item)].index.values[0] for item in my_lst]
my_fund_netvalue=get_extras('unit_net_value',result_security_lst,start_date='2022-01-01',end_date='2022-12-10',df=True)

my_fund_return=(my_fund_netvalue-my_fund_netvalue.shift(1))/my_fund_netvalue.shift(1)
my_fund_return.dropna(inplace=True)

print(my_fund_return.corr(),'\n')

my_fund_annual_GeoReturn=np.power((1+my_fund_return).product(),252/len(my_fund_return))-1  #用几何平均收益率方法,通过power函数取252次方,求出年度收益率
print(my_fund_annual_GeoReturn)
           000176.OF  001563.OF  005250.OF  014829.OF
000176.OF   1.000000   0.473110   0.888002   0.563668
001563.OF   0.473110   1.000000   0.401285   0.218663
005250.OF   0.888002   0.401285   1.000000   0.450688
014829.OF   0.563668   0.218663   0.450688   1.000000 

000176.OF    0.067738
001563.OF    0.245541
005250.OF   -0.011441
014829.OF   -0.227563
dtype: float64

        从上面的相关系数矩阵可以看到,这几只随意选出来的基金,相关系数分布都是正相关。我们来计算一下如何调整各只基金的投资比例,才能得到更具性价比的收益波动率组合。下面的代码定义了随机权重生成函数(weight_generate),投资组合的回报计算函数(年化几何平均收益率,由于此处是投资组合,需要通过权重计算整个组合的投资回报,因此不能使用对数收益率),投资组合的波动率计算函数(年化方差)。

#基于基金的历史收益,进行的有效前沿模拟。
#由于是有效前沿模拟,不需要用超额收益,而且需要用简单算数收益率来进行计算

%matplotlib qt5

def weight_generate(num,stock_lst):
    weight=np.random.uniform(1,100,size=[num,len(stock_lst)])
    weight=pd.DataFrame(weight)
    sum_df=weight.sum(axis=1)
    for col in weight.columns:
        weight[col]=weight[col]/sum_df
    return weight

#计算组合收益率
def portfolio_return(weight,mean_ret):
    print("This's my weight: ",weight)
    mean_ret=pd.DataFrame(mean_ret,columns=['ret_mean'])
    print("mean_ret is :",mean_ret['ret_mean'])
    ret_lst=[]
    for index,row in weight.iterrows():
        ret=(np.dot(row,mean_ret['ret_mean']))
        ret_lst.append(ret)
    return pd.DataFrame(ret_lst)

#计算组合方差
def portfolio_std(weight,cov):
    weight=np.array(weight)
    cov=np.array(cov)
    print(cov.shape)
    std_lst=[]
    for row in weight:
        weight_row=row.reshape(1,len(row))
        weight_row_T=row.reshape(len(row),1)
        std=np.sqrt(np.dot(np.dot(weight_row,cov),weight_row_T)*252)  #协方差矩阵是每日平均值,乘以252进行年化之后,得到年度的方差,开根号,得到标准差
        std_lst.append(std[0][0])
    return pd.DataFrame(std_lst)

        下面定义优化求解函数,func是求最小方差的优化函数,func2是求最小收益的优化函数我们的约束函数相对简单,就是挑选的四只基金的权重均>0%,同时权重加总为100%。这两个优化函数、约束函数最终需要传给scipy.optimize的minimize函数求解:

#求解最小方差组合(并非夏普比率最优的组合)----------------------------------------------------------------------
cov=my_fund_return.cov()
ret=my_fund_annual_GeoReturn
e=1e-10
def func(x,sign=1,cov=cov):
    cov=np.array(cov)
    weight=np.array([x[0],x[1],x[2],x[3]])
    weight_T=weight.T
    return sign*np.sqrt(np.dot(np.dot(weight,cov),weight_T)*252) 
def func2(x,sign=1,ret=ret):
    ret=np.array(ret)
    weight=np.array([x[0],x[1],x[2],x[3]])
    return sign*(np.dot(weight,ret)) 

cons=(
    {'type':'eq','fun': lambda x:x[0]+x[1]+x[2]+x[3]-1},  #w1+w2+w3+w4=100%   权重之和需等于100%
    {'type':'ineq','fun':lambda x:x[0]-e},
    {'type':'ineq','fun':lambda x:x[1]-e},
    {'type':'ineq','fun':lambda x:x[2]-e},
    {'type':'ineq','fun':lambda x:x[3]-e})
x0=np.array((1.0,1.0,1.0,1.0))
res=minimize(func,x0,method='SLSQP',constraints=cons)                      #最小化方差
res_min_ret=minimize(func2,x0,method="SLSQP",constraints=cons)             #最小化收益
print("最小值: ",res.fun)
print("最优解: ",res.x)
print("迭代终止是否成功:",res.success)
print("迭代终止原因:",res.message)

        进行10000次模拟,得出四只基金随机组合的收益、波动率组合图。同时把自有组合、最小方差组合、最小收益组合的【收益-波动率】点求出来后,分别画到图上。

#模拟1万次各种投资比例得到的投资组合的收益、波动率,画出散点图
weight=weight_generate(10000,my_lst)
p_return=portfolio_return(weight,my_fund_annual_GeoReturn)   #用自定义函数求出基金组合的总回报
p_std=portfolio_std(weight,my_fund_return.cov())             #用自定义函数求出基金组合的总波动率

p_result=pd.concat([p_return,p_std],axis=1)                  #把组合回报、组合波动率两列数据拼合成一个数据框
p_result.columns=['port_ret','port_std']
fig,ax=plt.subplots(figsize=(12,8))
plt.rcParams['font.sans-serif'] = ['SimHei']                 #设置中文显示
plt.rcParams['axes.unicode_minus']=False                     #防止坐标轴负号无法显示
ax.scatter(p_result['port_std'],p_result['port_ret'],cmap='summer',alpha=0.6)       #刻画散点图

# ax.set_xticklabels(rotation=80)
ax.set_xlabel('Portfolio std')
ax.set_ylabel('Portfolio ret')
ax.set_title("你的基金包:组合收益-波动率图")
#####################################################################################
#我们持有的组合,按照平均的比例、最小方差组合比例、最小收益组合比例进行投资,下面算出我们这种投资比例所得出的收益、波动率,并画点
my_weight=pd.DataFrame([[1/len(my_lst) for x in range(len(my_lst))],res.x,res_min_ret.x],columns=['w1','w2','w3','w4'])
my_return = portfolio_return(my_weight,my_fund_annual_GeoReturn)
my_std=portfolio_std(my_weight,my_fund_return.cov())
my_portfolio=pd.concat([my_return,my_std],axis=1)
my_portfolio.columns=['port_ret','port_std']
#####################################################################################
#分别画出最低波动率组合、自有组合的收益、波动率点
ax.scatter(my_portfolio['port_std'][0],my_portfolio['port_ret'][0],color='yellow',cmap='summer')
ax.annotate(text="你的基金组合",xy=(my_portfolio['port_std'][0],my_portfolio['port_ret'][0]),xytext=(my_portfolio['port_std'][0]+0.001,my_portfolio['port_ret'][0]+0.001),color='yellow')
ax.scatter(my_portfolio['port_std'][1],my_portfolio['port_ret'][1],color='red',cmap='summer')
ax.annotate(text="方差最小_基金组合",xy=(my_portfolio['port_std'][1],my_portfolio['port_ret'][1]),xytext=(my_portfolio['port_std'][1]+0.001,my_portfolio['port_ret'][1]+0.001),color='red')
ax.scatter(my_portfolio['port_std'][2],my_portfolio['port_ret'][2],color='orange',cmap='summer')
ax.annotate(text="收益最小_基金组合",xy=(my_portfolio['port_std'][2],my_portfolio['port_ret'][2]),xytext=(my_portfolio['port_std'][2]+0.001,my_portfolio['port_ret'][2]+0.001),color='orange')

1300d979fc194dd9b19d5cc632b9d538.png

        由上图可以看到,【最小方差组合】在【你的基金组合】左上方,意味着该组合比平均投入资金到四只基金里面的收益回报效果更好。打印出该组合的投资比例,我们可以得知若想得到该投资效果,四只基金的投资比例为:['70.26%', '7.95%', '10.5%', '11.29%']。目前仅仅只是考虑求取最小波动率而得出的组合,该投资组合的性价比已经比平均投入资金['25%', '25%', '25%', '25%']有所优化。而最左上边线,也就是我们常说的有效前沿上的组合,则是最具性价比的投资组合方式。

        但是算出来70.26%,7.95%,10.5%,11.29%的比率之后,各位小伙伴不能立刻严格按照这个比例来调整你们的资金投入比例。因为,该结果是用历史数据计算出来的,所依据的是历史的回报、波动率矩阵。如果需要最优化未来的投资回报、波动率比率,需要对未来的投资回报、波动率进行预测。然后再用预测出来的每只基金、波动率放入马科维茨模型里面,计算相对优化的投资比例。

        听起来稍显复杂,但是至少目前看来,对你的基金进行点评的时候,我们好歹有了基本的坐标,知道自己的每只基金的投资比例对我们投资组合的性价比产生明显影响。所以在投入资金的时候,其实需要根据每只基金的收益、波动特性,认真考虑要合理资金的分配的。

        改动一下上述代码,加入无风险利率,可以得出夏普比率最优的投资组合。我们把>最小方差组合的收益设为目标收益,用优化函数求出其对应的最小方差,画出【有效前沿】,下图中红色线。对于我们的最优夏普比率组合,明显只有一只基金,也就是说按照该组合的提示,你把口袋里的全部资金投入到【001563.OF】即可。这虽然是很明显的马后炮,但是如果你手里的水晶球告诉你,这四只基金接下来一年的管理会使其收益风险特征大致不变,那按照马科维茨模型的计算结果,如果凑巧你手里只有这四只基金可以选择,那最好的办法确实就是从无风险资产包基金【001563.OF】这两者之间,根据你个人的风险偏好进行资金的分配。      

88a2d0eb8d224eb592006d60375f9a62.png
        这次的练手先到这儿,后续有时间会继续深入挖掘一下马科维茨模型和CAPM这类模型的关系(等我把投资学重新撸一遍再说)。提前祝各位亲爱的小伙伴新年快乐,2023一切顺遂平安!

马科维茨股票投资组合模型是一个经典的投资理论,它是指基于资产之间的互相影响度,将不同资产按照一定比例组合,以达到优化风险收益比例的目的。马科维茨模型具有较高的实用价值,得到广泛应用,因此在MATLAB中实现该模型具有非常大的意义。 要建立马科维茨模型,需要确定投资组合中各个资产的收益率、风险以及相关系数等信息。在MATLAB中,可以通过获取资产的历史数据,进行数据处理和分析,从而得到这些信息。其中,投资组合的收益率可以通过资产的历史价格数据计算得到,而风险可以通过计算资产的方差和协方差矩阵得到。同时,需要注意的是,为了保证投资组合的有效性,资产权重的和必须为1。 在实现马科维茨模型时,需要按照以下步骤进行: 1.获取资产历史数据,并进行数据清洗和处理,得到资产的收益率、风险和相关系数等信息。 2.建立投资组合的优化模型,即最小化组合风险,同时最大化组合收益的模型。这可以使用MATLAB中的优化工具来实现,例如"fmincon"函数。 3.通过计算资产的协方差矩阵、均值和方差等信息,将结果进行可视化,以便分析和评估投资组合的表现。MATLAB中可以使用数据可视化工具,如"plot"和"scatter"函数来实现。 总之,MATLAB的实现为马科维茨股票投资组合模型提供了强大的工具,可以更好地对投资组合进行分析和预测,为投资决策提供数据支持和决策指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

外汇量化__炼丹房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值