財(cái)務(wù)數(shù)據(jù)挖掘?qū)崙?zhàn)用Python構(gòu)建基本面因子分析系統(tǒng) IG50免費(fèi)開源股票數(shù)據(jù)API接口)
全市場(chǎng)財(cái)務(wù)數(shù)據(jù)挖掘?qū)崙?zhàn)用Python構(gòu)建基本面因子分析系統(tǒng)做量化投資快六年了從最開始跟著網(wǎng)上的教程寫簡(jiǎn)單的均線策略到后來逐漸接觸多因子模型我最深的感觸是數(shù)據(jù)質(zhì)量和因子構(gòu)建邏輯直接決定了策略的上限。很多人花大量時(shí)間調(diào)參優(yōu)化卻忽略了最基礎(chǔ)的數(shù)據(jù)清洗和因子計(jì)算環(huán)節(jié)。這篇文章我想分享一下自己從零搭建全市場(chǎng)基本面因子分析系統(tǒng)的完整過程希望能幫到同樣在摸索的朋友。先說說為什么要做這個(gè)系統(tǒng)。市面上的量化平臺(tái)雖然方便但往往有幾個(gè)問題一是數(shù)據(jù)更新不及時(shí)二是因子計(jì)算不透明三是無法靈活地進(jìn)行自定義因子開發(fā)。所以從去年開始我決定自己搭一套本地的基本面因子分析系統(tǒng)。經(jīng)過一段時(shí)間的試錯(cuò)終于形成了一套相對(duì)穩(wěn)定的流程。整個(gè)系統(tǒng)的核心數(shù)據(jù)來源分三塊第一塊是全市場(chǎng)股票列表用來確定股票池第二塊是每只股票的財(cái)務(wù)指標(biāo)數(shù)據(jù)包括ROE、毛利率、凈利率這些關(guān)鍵指標(biāo)第三塊是實(shí)時(shí)行情數(shù)據(jù)用來獲取PE、PB、市值這些估值指標(biāo)。數(shù)據(jù)都存在本地用的時(shí)候直接讀文件速度比調(diào)接口快很多。先看數(shù)據(jù)讀取部分。我把數(shù)據(jù)都按統(tǒng)一的目錄結(jié)構(gòu)存放股票列表在base/gplist目錄下財(cái)務(wù)指標(biāo)在time/f10/fi/{股票代碼}實(shí)時(shí)行情在time/real/{股票代碼}。寫個(gè)讀取函數(shù)就行這里我習(xí)慣用pandas來處理。importjsonimportosimportpandasaspdimportnumpyasnpfrompathlibimportPath data_dirD:/stock_datadefread_gplist():file_pathos.path.join(data_dir,base,gplist)withopen(file_path,r,encodingutf-8)asf:datajson.load(f)dfpd.DataFrame(data)df.columns[dm,mc,jys,isCy,isKc,isSt,isNew]returndfdefread_fi(dm):file_pathos.path.join(data_dir,time,f10,fi,dm)ifnotos.path.exists(file_path):returnNonewithopen(file_path,r,encodingutf-8)asf:datajson.load(f)ifnotdata:returnNonedfpd.DataFrame(data)returndfdefread_real(dm):file_pathos.path.join(data_dir,time,real,dm)ifnotos.path.exists(file_path):returnNonewithopen(file_path,r,encodingutf-8)asf:datajson.load(f)returndata這里字段名我用了簡(jiǎn)寫dm代表股票代碼mc代表名稱jys是交易所isCy、isKc、isSt、isNew分別表示是否創(chuàng)業(yè)板、科創(chuàng)板、ST、新股。這樣寫代碼的時(shí)候簡(jiǎn)潔很多自己能看懂就行。接下來是數(shù)據(jù)清洗這一步非常關(guān)鍵。全市場(chǎng)有五千多只股票不是每只都適合做基本面分析的。首先要過濾掉ST股和新股ST股基本面有問題新股數(shù)據(jù)不完整都會(huì)影響因子分析的準(zhǔn)確性。然后還要考慮數(shù)據(jù)的完整性有些股票的財(cái)務(wù)指標(biāo)缺得厲害也要剔除。deffilter_stock_pool(df_gp):mask(df_gp[isSt]0)(df_gp[isNew]0)df_filtereddf_gp[mask].copy()returndf_filtered過濾完股票池就該計(jì)算因子了。我常用的基本面因子大概分幾類盈利能力因子、成長(zhǎng)能力因子、估值因子、償債能力因子。每一類下面又有具體的指標(biāo)。比如盈利能力因子里我會(huì)看ROE凈資產(chǎn)收益率、銷售毛利率、銷售凈利率這三個(gè)核心指標(biāo)。ROE是巴菲特最看重的指標(biāo)它反映了公司用股東的錢能賺多少回報(bào)。我一般取最近四個(gè)季度的加權(quán)凈資產(chǎn)收益率來計(jì)算。銷售毛利率和凈利率則反映了公司的定價(jià)能力和成本控制能力這兩個(gè)指標(biāo)結(jié)合起來看更有意義。defcalc_profit_factors(dm):df_firead_fi(dm)ifdf_fiisNoneorlen(df_fi)0:returnNonefactors{}roe_col加權(quán)凈資產(chǎn)收益率(%)ifroe_colindf_fi.columns:factors[roe]df_fi[roe_col].iloc[0]ifpd.notna(df_fi[roe_col].iloc[0])elsenp.nan gross_margin_col銷售毛利率(%)ifgross_margin_colindf_fi.columns:factors[gross_margin]df_fi[gross_margin_col].iloc[0]ifpd.notna(df_fi[gross_margin_col].iloc[0])elsenp.nan net_margin_col銷售凈利率(%)ifnet_margin_colindf_fi.columns:factors[net_margin]df_fi[net_margin_col].iloc[0]ifpd.notna(df_fi[net_margin_col].iloc[0])elsenp.nanreturnfactors成長(zhǎng)能力因子方面我主要看主營(yíng)業(yè)務(wù)收入增長(zhǎng)率和凈利潤(rùn)增長(zhǎng)率。這兩個(gè)指標(biāo)能反映公司的擴(kuò)張速度。不過這里要注意一個(gè)問題增長(zhǎng)率太高有時(shí)候不一定是好事可能是基數(shù)太低導(dǎo)致的。所以我通常會(huì)結(jié)合絕對(duì)值一起看或者做一個(gè)截尾處理把極端值去掉。估值因子就簡(jiǎn)單了直接從實(shí)時(shí)行情數(shù)據(jù)里取市盈率動(dòng)態(tài)、市凈率、總市值。這三個(gè)指標(biāo)分別代表了市場(chǎng)對(duì)公司盈利的定價(jià)、對(duì)凈資產(chǎn)的定價(jià)以及公司的規(guī)模大小。很多人喜歡用低PE、低PB選股但實(shí)際效果往往不如預(yù)期因?yàn)椴煌袠I(yè)的估值水平本來就不一樣這就涉及到后面要說的行業(yè)中性化處理。defcalc_valuation_factors(dm):real_dataread_real(dm)ifnotreal_data:returnNonefactors{}pe_col市盈率動(dòng)態(tài)ifpe_colinreal_data:factors[pe]real_data[pe_col]ifreal_data[pe_col]andreal_data[pe_col]0elsenp.nan pb_col市凈率ifpb_colinreal_data:factors[pb]real_data[pb_col]ifreal_data[pb_col]andreal_data[pb_col]0elsenp.nan mv_col總市值元ifmv_colinreal_data:factors[mv]real_data[mv_col]ifreal_data[mv_col]elsenp.nanreturnfactors現(xiàn)在因子計(jì)算出來了但還不能直接用來選股。最大的問題就是行業(yè)差異。比如銀行股的PE普遍在5-10倍而科技股的PE可能幾十上百倍你直接把所有股票放一起按PE排序選出來的肯定都是銀行、地產(chǎn)這些低估值行業(yè)的股票根本達(dá)不到分散配置的效果。這時(shí)候就需要做行業(yè)中性化處理。所謂行業(yè)中性化就是把每個(gè)因子在行業(yè)內(nèi)部做標(biāo)準(zhǔn)化處理消除行業(yè)之間的系統(tǒng)性差異。具體做法是先給每只股票打上行業(yè)標(biāo)簽然后按行業(yè)分組在組內(nèi)計(jì)算因子的Z-score也就是減去均值再除以標(biāo)準(zhǔn)差。這樣處理后不同行業(yè)的因子值就有了可比性。defneutralize_by_industry(df,factor_cols,industry_colhy):df_resultdf.copy()forcolinfactor_cols:df_result[col_neutral]np.nanforhy,groupindf_result.groupby(industry_col):iflen(group)5:continuevalsgroup[col].values mean_valnp.nanmean(vals)std_valnp.nanstd(vals)ifstd_val0ornp.isnan(std_val):continueneutral_vals(vals-mean_val)/std_val df_result.loc[group.index,col_neutral]neutral_valsreturndf_result說到行業(yè)標(biāo)簽這里還有個(gè)小問題。股票列表數(shù)據(jù)里沒有直接的行業(yè)分類我是通過所屬板塊數(shù)據(jù)來獲取的。每只股票的所屬板塊信息在time/f10/ssbk/{股票代碼}目錄下里面有概念板塊和行業(yè)板塊的信息。我一般用申萬行業(yè)分類作為標(biāo)準(zhǔn)從所屬板塊數(shù)據(jù)里提取出申萬一級(jí)行業(yè)。defget_industry(dm):file_pathos.path.join(data_dir,time,f10,ssbk,dm)ifnotos.path.exists(file_path):return其他withopen(file_path,r,encodingutf-8)asf:datajson.load(f)foritemindata:if所屬板塊initem.get(關(guān)鍵字,):contentitem.get(內(nèi)容,)forpartincontent.split( ):ifpart.startswith(申萬)and行業(yè)inpart:returnpartreturn其他行業(yè)中性化之后就可以把多個(gè)因子合成一個(gè)綜合打分了。最簡(jiǎn)單的方法就是等權(quán)平均每個(gè)因子的中性化值加起來除以因子個(gè)數(shù)。如果對(duì)某些因子更有信心也可以給不同的權(quán)重。我個(gè)人比較喜歡用等權(quán)因?yàn)楹?jiǎn)單透明不容易過擬合。defcalc_composite_score(df,neutral_cols):df_resultdf.copy()df_result[composite_score]df_result[neutral_cols].mean(axis1)returndf_result最后就是排名選股了。把所有股票按綜合打分從高到低排序選前N只就行。N的大小取決于你的資金量一般選30-50只比較合適既能分散風(fēng)險(xiǎn)又不會(huì)因?yàn)楣善碧喽芾聿贿^來。defselect_stocks(df,n30):df_sorteddf.sort_values(composite_score,ascendingFalse)returndf_sorted.head(n)把這些步驟串起來就是一個(gè)完整的基本面因子分析流程。我一般每周跑一次先更新股票列表然后批量讀取財(cái)務(wù)指標(biāo)和行情數(shù)據(jù)計(jì)算因子、中性化、打分選股。整個(gè)過程五千多只股票跑下來大概十幾分鐘效率還可以。實(shí)際使用下來這套系統(tǒng)有幾個(gè)明顯的好處。第一是數(shù)據(jù)完全可控不用擔(dān)心平臺(tái)突然停服或者數(shù)據(jù)出錯(cuò)第二是因子計(jì)算邏輯透明可以隨時(shí)調(diào)整優(yōu)化第三是回測(cè)方便因?yàn)樗袛?shù)據(jù)都在本地可以很容易地做歷史回測(cè)驗(yàn)證因子的有效性。當(dāng)然也有一些需要注意的地方。財(cái)務(wù)指標(biāo)的數(shù)據(jù)更新頻率是每天15:30而且是近四個(gè)季度的數(shù)據(jù)所以做回測(cè)的時(shí)候要注意避免未來函數(shù)。還有就是因子的有效性不是一成不變的有些因子在某些市場(chǎng)環(huán)境下表現(xiàn)好換個(gè)環(huán)境可能就失效了所以需要定期評(píng)估和更新因子庫。另外我還做了一些擴(kuò)展功能。比如在因子計(jì)算之后會(huì)檢查每只股票的十大股東和股東變化趨勢(shì)如果發(fā)現(xiàn)機(jī)構(gòu)持倉(cāng)比例高、股東戶數(shù)持續(xù)減少的會(huì)額外加分。還會(huì)結(jié)合資金流向數(shù)據(jù)看看最近主力資金的動(dòng)向作為輔助參考。總的來說搭建這套系統(tǒng)花了不少時(shí)間但確實(shí)是值得的。它讓我對(duì)基本面量化有了更深刻的理解也讓我的投資決策更加理性和系統(tǒng)。如果你也在做類似的事情希望這篇文章能給你一些啟發(fā)。最后說一下我用的數(shù)據(jù)來源。數(shù)據(jù)來自ig50的本地?cái)?shù)據(jù)接口數(shù)據(jù)結(jié)構(gòu)清晰字段定義明確做二次開發(fā)很方便。有需要的朋友可以自己去了解一下。接口說明base/gplist - 滬深京A股列表本地路徑數(shù)據(jù)存放目錄/base/gplist字段說明股票代碼(dm)、股票名稱(mc)、交易所(jys)、是否創(chuàng)業(yè)板(isCy)、是否科創(chuàng)板(isKc)、是否ST(isSt)、是否新股(isNew)time/f10/fi/{股票代碼} - 財(cái)務(wù)指標(biāo)本地路徑數(shù)據(jù)存放目錄/time/f10/fi/{股票代碼}主要字段報(bào)告日期、加權(quán)凈資產(chǎn)收益率(%)、銷售毛利率(%)、銷售凈利率(%)、主營(yíng)業(yè)務(wù)利潤(rùn)率(%)、總資產(chǎn)凈利潤(rùn)率(%)、營(yíng)業(yè)利潤(rùn)率(%)、主營(yíng)業(yè)務(wù)收入增長(zhǎng)率(%)、凈利潤(rùn)增長(zhǎng)率(%)、凈資產(chǎn)增長(zhǎng)率(%)、總資產(chǎn)增長(zhǎng)率(%)、流動(dòng)比率、速動(dòng)比率、資產(chǎn)負(fù)債率(%)等time/real/{股票代碼} - 實(shí)時(shí)行情數(shù)據(jù)本地路徑數(shù)據(jù)存放目錄/time/real/{股票代碼}主要字段代碼、名稱、當(dāng)前價(jià)格、昨收價(jià)、漲跌幅(%)、總市值(元)、流通市值(元)、市盈率(動(dòng)態(tài))、市盈率TTM、市凈率、每股收益、每股凈資產(chǎn)、換手率(%)等time/f10/ssbk/{股票代碼} - 所屬板塊本地路徑數(shù)據(jù)存放目錄/time/f10/ssbk/{股票代碼}主要字段關(guān)鍵字、內(nèi)容包含行業(yè)板塊、概念板塊等信息gitee開源地址github開源地址