Python分析三季度基金調倉


目前三季度已經過去了一個月,大部分基金都已經公布了三季度持倉數據,今天我們就用 Python 分析以下今年三個季度基金的調倉情況。

獲取數據

第一步,我們要獲取目前發行的所有基金及其持有的股票。

可以寫一個爬蟲去基金網站爬數據,但太麻煩,這里其實是有捷徑的。有些朋友可能聽說過量化投資,這些做量化投資的平台都會提供金融數據,我們只要安裝相應的 Python 包就可以獲取股票、基金、債券相關的數據。

本次分析用的是聚寬(https://www.joinquant.com/)平台,新用戶注冊后會有6個月試用期,期間可以免費使用平台所有數據,每天可調用100萬條數據,完全夠我們分析了。

img

注冊賬號后,我們就可以調用聚寬的數據。以下代碼我用 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 函數,獲取所有的基金,該函數文檔如下

img

代碼如下:

df = get_all_securities(['fund', 'open_fund'], '2021-11-10')
df

img

df 里可能會有重復基金,按照基金代碼(df.index)去重看看共獲取了多少只基金

code_arr = list(set([x.split('.')[0] for x in df.index.values]))
len(code_arr)

共返回 13698 支基金,這可比爬蟲來得快多了。

獲取股票投資占比

這里我只想關注股票類型的基金,所以需要獲取每支基金股票投資的占比,股票投資占比小於 50% 的,剔除掉。

img

從文檔來看,查詢 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

img

篩選 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,過濾掉了一半。

獲取基金持倉

獲取基金持倉的方式跟上面一樣,只不過表名和字段名變了。

編寫 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 的信息

img

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

調倉方向

這里我想按照行業來看,如:從第一季度到第三季度,有多少基金購買了新能源股票,有多少基金購買了白酒股票。所以,就需要將股票對應到行業上。

嚴格來說應該找權威的券商,將每個股票都映射成某個行業。不過這里我只是想定性分析一下,所以找了一些行業指數基金的前 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']

img

有了行業信息,就可以用透視表直接統計每個行業在三個季度基金數量的變化

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

img

從圖上還是能發現一些明顯的特征的,持倉新能源的基金不光基數大,增速速度還很快。比較意外的是白酒的持倉基金數量居然是下降的。

探索更有意思的

由於我們能夠拿到所有的基金,其實我們還可以探索的東西有很多。比如:可以做出如下效果的數據

img

每一行都代表一只基金,列 rank1 ~ rank10 代表該基金前十大重倉股,逗號前半部分是持倉股票,后半部分是持倉占比。標紅代表是我們關注的股票,最后一列是標紅股票持倉占比的加和。

這種展示方式有很多好處,比支付寶、同花順軟件還好。

第一,支持按照某些股票篩選基金(上圖標紅的部分),我了解的基金軟件都只能按照一只股票篩選基金

第二,可以計算包含這些股票總持倉占比(candid_prop列),總持倉越高跟我們預期越接近。而現有的基金軟件都要自己手動加

第三,一行展示基金所有持倉,一目了然,而基金軟件看不同基金的持倉需要在不同基金之間來回切頁面,非常不方便。

最后,介紹下上圖的實現代碼。首先,只篩選最新季度持倉即可

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

img

定義函數,計算候選股票集合的總持倉占比

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

這里我們只分析了基金數據,聚寬平台還包括股票、債券等其他金融數據,獲取方式跟上面講的一樣。有興趣的朋友可以試試。

源文件已經整理好,關注公眾號 渡碼 回復關鍵詞 基金調倉 即可。

duma


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM