Python分析三季度基金调仓

菜鸟学Python

共 2768字,需浏览 6分钟

 ·

2021-12-20 02:35

目前三季度已经过去了一个月,大部分基金都已经公布了三季度持仓数据,今天我们就用 Python 分析以下今年三个季度基金的调仓情况。

1

 获取数据



第一步,我们要获取目前发行的所有基金及其持有的股票。
可以写一个爬虫去基金网站爬数据,但太麻烦,这里其实是有捷径的。有些朋友可能听说过量化投资,这些做量化投资的平台都会提供金融数据,我们只要安装相应的 Python 包就可以获取股票、基金、债券相关的数据。
本次分析用的是聚宽(https://www.joinquant.com/)平台,新用户注册后会有6个月试用期,期间可以免费使用平台所有数据,每天可调用100万条数据,完全够我们分析了。


注册账号后,我们就可以调用聚宽的数据。以下代码我用 Python 3.8+jupyter 编写、运行。
导入包,获取聚宽授权
from jqdatasdk import *
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # mac matplot显示中文
plt.rcParams['axes.unicode_minus'] = False


auth('聚宽账户', '聚宽密码') # 聚宽授权


调用聚宽 get_all_securities 函数,获取所有的基金,该函数文档如下


代码如下:
df = get_all_securities(['fund', 'open_fund'], '2021-11-10')
df




df 里可能会有重复基金,按照基金代码(df.index)去重看看共获取了多少只基金
code_arr = list(set([x.split('.')[0] for x in df.index.values]))
len(code_arr)


共返回 13698 支基金,这可比爬虫来得快多了。


2

 获取股票投资占比



这里我只想关注股票类型的基金,所以需要获取每支基金股票投资的占比,股票投资占比小于 50% 的,剔除掉。



从文档来看,查询 FUND_PORTFOLIO 表的 stock_rate 字段就可以获取基金的股票投资占比。查询方式是用 query 函数构造查询语句,然后调用 finance.run_query 函数完成查询。
写一个函数,用来返回查询语句
def asset_query(arr):
    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_(arr),
                finance.FUND_PORTFOLIO.period_end.in_(['2021-03-31', '2021-06-30', '2021-09-30']),
                finance.FUND_PORTFOLIO.report_type.in_(['第一季度', '第二季度', '第三季度']))
    return q


asset_query 函数的参数 arr 是个数组,存放基金代码。query函数指定返回的字段。filter 函数指定需要筛选的数据,code 代表基金代码;period_end 是报告期(每个季度最后一天),填入的三个日期分别代表每个季度;report_type 是报告类型,填入的是三个季度,添加该条件是为了只读取单季度的数据,不需要半年或年度数据。最终返回查询语句 q。
asset_query 函数生成对象 q 的用的是 SQLAlchemy 提供的语法规则,SQLAlchemy 是Python 中著名的 ORM 框架,简单来说它提供以 Python 代码的方式查询数据库,而不用写 SQL。感兴趣的朋友可以学习下,能够帮你实现更复杂的查询逻辑。
运行 finance.run_query 来执行查询
i = 0

while i < len(code_arr):
    print('获取基金资产 ' + str(i))
    tmp_arr = code_arr[i: i+1500]
    q = asset_query(tmp_arr)
    tmp_df = finance.run_query(q)
    
    if i == 0:
        asset_df = tmp_df
    else:
        asset_df = pd.concat([asset_df, tmp_df])
    
    i += 1500


因为 finance.run_query 单次最多返回 5000 行数据,所以这里写了个循环分批读取,一次只读取 1500 个基金的数据。因为一个基金返回 3 个季度数据,所以一次读取最多返回 4500 数据,不会超限。
读取完成后,看看 asset_df 


筛选 stock_rate 大于 50 的基金
stock_fund_df = asset_df[asset_df['stock_rate'] > 50]


统计筛选后,还剩多少只基金
stock_fund_code_arr = list(set(stock_fund_df['code']))
len(stock_fund_code_arr)


返回 5590,过滤掉了一半。

3

 获取基金持仓


获取基金持仓的方式跟上面一样,只不过表名和字段名变了。
编写 hold_stock_query 函数,生成相应的查询语句
def hold_stock_query(arr):
    q=query(finance.FUND_PORTFOLIO_STOCK
       ).filter(finance.FUND_PORTFOLIO_STOCK.code.in_(arr),
                finance.FUND_PORTFOLIO_STOCK.rank <= 10,
                finance.FUND_PORTFOLIO_STOCK.period_end.in_(['2021-03-31', '2021-06-30', '2021-09-30']),
                finance.FUND_PORTFOLIO_STOCK.report_type.in_(['第一季度', '第二季度', '第三季度']))
    return q


finance.FUND_PORTFOLIO_STOCK.rank <= 10代表只获取前十大重仓股。
同样通过循环的方式查询数据
i = 0

while i < len(stock_fund_code_arr):
    print('获取基金持仓 ' + str(i))
    tmp_arr = stock_fund_code_arr[i: i+150]
    q = hold_stock_query(tmp_arr)
    tmp_df=finance.run_query(q)
    
    if i == 0:
        hold_stock_df = tmp_df
    else:
        hold_stock_df = pd.concat([hold_stock_df, tmp_df])
    
    i += 150

hold_stock_df


看看 hold_stock_df 的信息


name是持有的股票名,rank是持有该股票的排名(1 代表该基金第一大重仓股),proportion是持仓该股票比例。

4

 调仓方向


这里我想按照行业来看,如:从第一季度到第三季度,有多少基金购买了新能源股票,有多少基金购买了白酒股票。所以,就需要将股票对应到行业上。
严格来说应该找权威的券商,将每个股票都映射成某个行业。不过这里我只是想定性分析一下,所以找了一些行业指数基金的前 6 大重仓股来代替某个行业。映射关系如下
stock_to_industry_dict = {
    '比亚迪':'新能源',
    '宁德时代':'新能源',
    '恩捷股份':'新能源',
    '赣锋锂业':'新能源',
    '亿纬锂能':'新能源',
    '汇川技术':'新能源',
    '圣邦股份':'半导体',
    '韦尔股份':'半导体',
    '卓胜微':'半导体',
    '北方华创':'半导体',
    '兆易创新':'半导体',
    '三安光电':'半导体',
    '贵州茅台':'白酒',
    '五粮液':'白酒',
    '山西汾酒':'白酒',
    '泸州老窖':'白酒',
    '洋河股份':'白酒',
    '酒鬼酒':'白酒',
    '阳关电源':'光伏',
    '通威股份':'光伏',
    '中环股份':'光伏',
    '隆基股份':'光伏',
    '特变电工':'光伏',
    '正泰电器':'光伏',
    '药明康德':'医疗',
    '智飞生物':'医疗',
    '沃森生物':'医疗',
    '泰格医药':'医疗',
    '长春高新':'医疗',
    '凯莱英':'医疗',
    '复星医药':'医疗',
}

向 hold_stock_df 添加一列 industry,代表持有该股票所对应的行业
def stock_to_industry(cols):
    if cols['name'] in stock_to_industry_dict:
        return stock_to_industry_dict[cols['name']]
    return '无'

hold_stock_df['industry'] = hold_stock_df.apply(lambda x: stock_to_industry(x), axis=1)
hold_stock_df


找一个新能源基金看下效果
hold_stock_df[hold_stock_df['code'] == '005939']



有了行业信息,就可以用透视表直接统计每个行业在三个季度基金数量的变化
industry_count_df = hold_stock_df.pivot_table(index='industry', 
                                    columns=['report_type'],
                                    values='code',
                                    aggfunc=lambda x:len(x.unique()))

cols_name = ['第一季度', '第二季度', '第三季度']
industry_count_df = industry_count_df[cols_name]
industry_count_df.drop(['无'], inplace=True)

plt.rcParams['figure.figsize'] = (12, 8)
industry_count_df.plot.bar()



从图上还是能发现一些明显的特征的,持仓新能源的基金不光基数大,增速速度还很快。比较意外的是白酒的持仓基金数量居然是下降的。

5

 探索更有意思的


由于我们能够拿到所有的基金,其实我们还可以探索的东西有很多。比如:可以做出如下效果的数据


每一行都代表一只基金,列 rank1 ~ rank10 代表该基金前十大重仓股,逗号前半部分是持仓股票,后半部分是持仓占比。标红代表是我们关注的股票,最后一列是标红股票持仓占比的加和。
这种展示方式有很多好处,比支付宝、同花顺软件还好。
第一,支持按照某些股票筛选基金(上图标红的部分),我了解的基金软件都只能按照一只股票筛选基金
第二,可以计算包含这些股票总持仓占比(candid_prop列),总持仓越高跟我们预期越接近。而现有的基金软件都要自己手动加
第三,一行展示基金所有持仓,一目了然,而基金软件看不同基金的持仓需要在不同基金之间来回切页面,非常不方便。
写到这里我都想用 Python 封装一个可视化基金筛选工具了,需要我做的朋友可以留言或者文末点个在看,如果人数多,下期就分享。
最后,介绍下上图的实现代码。首先,只筛选最新季度持仓即可
hold_stock_p3_df = hold_stock_df[hold_stock_df['report_type'] == '第三季度']

构造两个新列
hold_stock_p3_df['hold_rank'] = ['rank%d' % i for i in hold_stock_p3_df['rank']]
hold_stock_p3_df['hold_info'] = hold_stock_p3_df['name'] + " : " + hold_stock_p3_df['proportion'].astype(str)


用透视图,列转行
tmp_df = hold_stock_p3_df.pivot(index='code', columns='hold_rank', values='hold_info')
tmp_idx = tmp_df.index
tmp_df = tmp_df.reset_index()
tmp_df['name'] = tmp_idx

rank_cols = ['rank%d' % i for i in range(1, 11)]
col_names = ['code'] + rank_cols

final_fund_df = tmp_df[col_names]

final_fund_df



定义函数,计算候选股票集合的总持仓占比
candid_fund_list = ['宁德时代','比亚迪', '恩捷股份', '璞泰来', '诺德股份']


def filter_fund(x):
    all_prop = 0
    for i in range(1, 11):
        col = 'rank' + str(i)
        vals = str(x[col]).split(':')
        if vals[0].strip() in candid_fund_list:
            all_prop += float(vals[1].strip())
    return all_prop


定义函数用于上色
def show_color(val):
    color = '#BB0000' if str(val).split(':')[0].strip() in candid_fund_list else ''
    return 'color:%s' % color


修改 condid_fund_list 的值,就可以股票筛选相关的基金,实现本节开头那张图的效果
final_fund_df['candid_prop'] = final_fund_df.apply(lambda x: filter_fund(x), axis=1)
tmp_fund_df = final_fund_df[final_fund_df['candid_prop'] > 0].sort_values(by='candid_prop', ascending=False)
tmp_fund_df = tmp_fund_df.style.applymap(show_color)
tmp_fund_df


这里我们只分析了基金数据,聚宽平台还包括股票、债券等其他金融数据,获取方式跟上面讲的一样。有兴趣的朋友可以试试。
源文件已经整理好,后台输入关键词 小助手,找他,暗号 基金 即可。如果本文对你有用就点个 在看 鼓励一下吧。



最后送大家一张我们投资星球的优惠券,星球主要是交流理财相关的知识,目前涉及基金,股票和其他的一些投资品种,里面高手很多,有基金今年赚89%的高手,还有一些投资其他品种赚6倍的大神。投资群信息差非常非常重要,需要抱团取暖才能走的远,有兴趣的赶紧上车。

学投资,交个朋友




推荐阅读:

入门: 最全的零基础学Python的问题  | 零基础学了8个月的Python  | 实战项目 |学Python就是这条捷径


干货:爬取豆瓣短评,电影《后来的我们》 | 38年NBA最佳球员分析 |   从万众期待到口碑扑街!唐探3令人失望  | 笑看新倚天屠龙记 | 灯谜答题王 |用Python做个海量小姐姐素描图 |碟中谍这么火,我用机器学习做个迷你推荐系统电影


趣味:弹球游戏  | 九宫格  | 漂亮的花 | 两百行Python《天天酷跑》游戏!


AI: 会做诗的机器人 | 给图片上色 | 预测收入 | 碟中谍这么火,我用机器学习做个迷你推荐系统电影


小工具: Pdf转Word,轻松搞定表格和水印! | 一键把html网页保存为pdf!|  再见PDF提取收费! | 用90行代码打造最强PDF转换器,word、PPT、excel、markdown、html一键转换 | 制作一款钉钉低价机票提示器! |60行代码做了一个语音壁纸切换器天天看小姐姐!


年度爆款文案


点阅读原文,看200个Python案例!

浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报