目錄
一、寫在前面
二、緣起
三、描繪一個現實中的理想世界
1:預期收益率r
2:下一期股利與增長率g
3:永續增長率g
4:內在價值計算
四、模型結果
五、寫在后面
一、寫在前面
1:本文主要討論戈登增長模型的優化及現實意義;另外,本文使用python實作模型并以某證券為例進行估值
2:本文主要行情資料通過Tushare(ID:444829)金融大資料平臺介面獲取, 部分財務指標,利率指標通過爬蟲獲取
3:筆者希望搭建出一套交易體系,原則是只做干貨的分享,后續將更新更多模塊,但作業學習之余的閑暇時間有限,更新速度慢還請諒解
4:文中假設與觀點是基于筆者對模型及資料的一孔之見,若有不同見解歡迎隨時留言交流
5:模型實作基于python3.8
二、緣起
先來段開場白,筆者認為一些理念性的東西價值遠超過代碼和模型本身,不過對長篇大論不感興趣的小伙伴們可以直接跳過,從第三部分開始看~
戈登增長模型(Gordon Growth Model, GGM) 筆者常戲稱之為哥哥們, 戈登增長模型其實是股息貼現模型(DDM)的一種優化,通過計算公司預期未來支付給股東的股利現值,來確定股票的內在價值,即是股利永續流入,GGM在上世紀五十年代被John Burr Williams發布出來,而它的弟弟——DDM則早在上世紀三十年度被提出,事實上,那個時代對股票的理念便是買入并持有以賺取分紅,現如今近百年過去,這樣的理念早被急不可耐的"行為金融學大師們"拋棄;尤其在A股,市場上的投資者多為買入并持有以賺取差價,
巴菲特說過:“如果沒有持有一支股票十年的打算就不要買入”,老人家的底氣其實可以用這樣的公式解釋:站在今天看未來可以獲得的好處,如果超過今天所付出的對價那便賺了,


在筆者眼中這是比薛定諤的貓還要高維度的存在——這個盒子里明天有沒有貓,作為一個唯物主義無神論者,我甚至沒有能力知道現在盒子里是否有貓,更遑論未來股價是多少? 事實是小到小散們的怒擲一手的交易,大到分析師報告里的紙上談兵( 買!),只要不是有能力畫K線的的主力,或是內幕的知(lao)情(shu)人(cang),我們都在拼命的引入未來函式,
什么?巴菲特?他老人家也引入了,不然20年疫情抄底航空股一抄抄在半山腰?

(本文只論基本面,老纏師請不要與我論道)佛陀有言:“一切有為法,皆待緣而起”,世間的結果都是因各種因緣而成,即為緣起,股價和股利亦是如此,筆者認為大可不必視未來函式如洪水猛獸,但其信號漂移是一定需要考慮的,我們所能做的就是加上限定條件,例如給我一只半透明的盒子或是一桿秤,那么可以通過觀察盒子或是稱重量間接知道盒子里是不是有貓,
事實情況卻遠比薛定諤的貓來得復雜:首先它站在空間維度(今天會不會有貓);對一個企業來說,目前所處行業,財務水平,管理層質量等等判斷企業(盒子)會不會大概率產生持續的利潤(貓),其次是站在時間維度(明天會不會有貓)導致各種信號產生的漂移,這是痛點所在,股利是否能永續流入?企業能否以目前狀況永續經營?外部黑天鵝事件,外部沖擊et al. 萬事皆可謂“緣起” ,
第二種情況較為復雜,出于內容規劃考慮,筆者想在后續模塊中尋求更好的解法,
第一種情況較為簡單,但由于N-holding period 太過苛刻,對權益工具來說難以實作,本文就從相對現實一些的GGM切入,
三、描繪一個現實中的理想世界
先為新世界匯入必要的模塊
import pandas as pd
import tushare as ts
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
import seaborn as sns
為了求出股價必須先求出:
1:預期收益率r
2:下一期股利與增長率g
3:永續增長率g
為保證模型有效性,建議采用10年的資料,5年也可,接下來以某市場指數, 及某證券自2011年1月1日起,至2021年9月7日所有交易日資料為例進行演示(為避免薦股嫌疑就不展示具體代碼了,下面相關資訊也都會隱去),以下結果均是通過row data計算得到,自己計算各種指標的最大的好處就是將一切問題可控,且可以進行很強的個性化處理,
1:預期收益率r
r 的求法有很多,這里通過CAPM實作,即
先來個可愛的迷你爬蟲獲取最新的國債利率做為無風險收益率rf,如果嫌麻煩也可以做好模型直接匯入具體數值,table存的是國債收益率表,三年到十年都有,這里取三個月短期國債收益率做為無風險收益率,即table.loc[1,2],
def risk_free(Date): ## Date: year-month-day
url = "https://yield.chinabond.com.cn/cbweb-cbrc-web/cbrc/historyQuery?startDate=" +
Date+ "&endDate=" + Date+ "&gjqx=0&qxId=ycqx&locale=cn_ZH&mark=1 "
content = pd.read_html(url)
table = content[1]
print('Risk_free rate(Short term (3 month) T-Bill):', table.loc[1, 2], "%")
return table.loc[1, 2]
接著是市場回報及敏感系數beta
這里引入一款非常方便的資料獲取模塊tushare,通過簡潔的資料介面可以獲取各類金融資料,省去大把寫爬蟲的時間,通過這個鏈接Tushare大資料社區注冊成功后即可通過自己的token便捷呼叫資料,具體可參考Tushare的技術檔案,
只需要對pro.daily傳入證券代碼及起止日期即可獲取所有日線級別資料,并如法炮制獲取市場指數資料,
def date_input(token, cp, mk, start, end):
pro = ts.pro_api(token)
cp = pro.daily(ts_code=cp, start_date=start, end_date=end) #company資料
mk = pro.index_daily(ts_code=mk, start_date=start, end_date=end) #market資料
beta = beta_value(cp, mk) # 計算beat
Mr = mean_value(mk) # 計算市場回報率
使用sklearn對榷訓報進行擬合,這里對沒有資料的停牌交易日進行剔除以保證指數與個股資料是成對出現,
def beta_value(cp, mk):
for i in mk['trade_date'].values:
if i not in cp['trade_date'].values:
# Match the market data-set with securities, some securities may halt
mk = mk[~mk['trade_date'].isin([i])] # 剔除個股停牌日
return_cp = cp ["pct_chg"]
return_mk = mk ["pct_chg"]
return_cp = return_cp.reshape(-1, 1)
return_cp = return_cp[::-1]
return_mk = return_mk.reshape(-1, 1)
return_mk = return_mk[::-1]
Mod = LinearRegression()
Mod.fit(return_mk, return_cp) # 采用線性回歸對市場及個股資料進行擬合
beta = Mod.coef_[0][0] # 計算出相關系數,即β
print("Beta is", beta)
# print("R_square是", Model.score(return_mk, return_cp))
return beta
計算得該證券
既然得到了十多年的資料,手癢順便看下中心趨勢及分布狀況是什么樣的
def dis_function(cp, mk):
m, s = pd.Series(mk["pct_chg"]), pd.Series(cp["pct_chg"])
print("market mean:",mk["pct_chg"].mean(),"market median:",mk["pct_chg"].median())
print("security mean", cp["pct_chg"].mean(), "security median:",cp["pct_chg"].median())
print("market skewness:",m.skew(),"market kurtosis:",m.kurt())
print("security skewness:",s.skew(),"security kurtosis:",s.kurt())
dataset = pd.DataFrame({"market": mk["pct_chg"].values, "security":
cp["pct_chg"].values})
sns.jointplot(x= "security", y = "market", data = dataset, kind = "reg")
plt.show()
可得:


近似是個正態,關于分布筆者會在后續的文章中用到,這里僅作展示,
接著把指數的日漲跌幅均值計算出來并年化處理:
def mean_value(mk):
mean = mk['pct_chg'].mean()
mean_yield = ((1 + mean / 100) ** 365 - 1) * 100
print("Average return of the Market(Suggest 10 years):", mean_yield, "%")
return mean_yield
計算得
至此,我們便得到了所有計算回報率 r 的要件,呼叫之前的模塊并計算r為:
rf = float(risk_free()) # Calculate risk free rate
beta_Mr = data_input(company, market, start, end)
beta = beta_Mr[0] # Beat value
Mr = beta_Mr[1]
expt_r = rf + beta * (Mr - rf)
計算可得E(r): 5.1745 %
2:下一期股利與增長率g
第一個迷之信仰來了:現在沒有分紅,如何知道下一期股利?結合增長率g來看大概有幾種解法:
1):公式法
通過ROE*(1-Payout Ratio)求出增長率g,這個增長率即是假設公司的利潤率不變,盈利效率不變,財務政策不變的情況下,可以實作的增長率,于是再使用當期股利乘以這個增長率以計算出下一期股利 ,
2):歷史資料法
通過過去推未來,計算過去一段時間內的股利年化增長率,再用當期股利乘以這個增長率以計算出下期股利
3):夸下海口法
由以上兩種方式計算出g,根據分析師靈(pou)敏(gai)的職業判斷對計算的g進行調整并向投資者夸下海口,
如果結合內幕訊息或是撞大運正好猜中,我們可以得到第四種解法:
4):什么都不干,直接把已知的下一期股利放入公式,
可以看到以上三種方式都用到了增長率g的概念,后面會再對g進行討論,接下來先以公式法為例進行計算,
首先來個精致的爬蟲從新浪上獲取歷史股利資訊:
def craw_dividend(company): #start, end,
url = "http://vip.stock.finance.sina.com.cn/corp/go.php/vISSUE_ShareBonus/stockid/" \
+ str(company.split(".")[0]) + ".phtml"
table = pd.read_html(url, attrs={"id": "sharebonus_1"}, header=[2])
div_inform = pd.DataFrame(table[0][::-1])
print出表格一看,什,,什么!一年竟然分好幾次紅公司和不分紅的鐵公雞?處理一下表格,不分紅為0,分紅多次的求其合計數做為該年股利,
# 接上一個代碼塊
year = []
div = []
div_hist = div_inform["派息(稅前)(元)"]
for i in div_inform["公告日期"]:
row = div_inform[(div_inform["公告日期"] == i)].index.tolist()
div.append(float(div_hist[row].values))
year.append(i.split("-")[0])
dataset = {"year": year, "dividend": div}
div_table = pd.DataFrame(data=dataset) # 只包含分紅資訊和年份的表格
rows_list = []
for i in div_table["year"]:
index = div_table[(div_table["year"] == i)].index.tolist()
rows_list.append(div_table[(div_table["year"] == i)].index.tolist()) if index not in
rows_list else next
for rows in rows_list:
if len(rows) == 1:
continue
else:
total_div = 0
for row in range(1, len(rows)):
total_div = total_div + div_table["dividend"][rows[row]]
div_table.drop(rows[row], inplace=True)
div_table.loc[rows[0], "dividend"] = total_div + div_table["dividend"][rows[0]]
div_table.reset_index(drop=None, inplace=True)
print(div_table)
return div_table, div_inform ## divd_inform has all div. information,div_table only has【year】and 【div】
這里留了個口子把div_inform 也取了出來,方便以后寫程式可以直接呼叫,
得到div_table如下:

感興趣的小伙伴可以通過歷史資料法計算推出下一期股利,這里采用公式法只取出當期股利,
3:永續增長率g
通過ROE*(1-Payout Ratio)求出增長率g
繼續一個蠢萌的迷你爬蟲獲取ROE
def craw_roe(company):
url
="http://money.finance.sina.com.cn/corp/go.php/vFD_FinancialGuideLine/stockid/"+str(company.split(".")[0])+"/ctrl/2019/displaytype/4.phtml"
ratio = pd.read_html(url, attrs={"id":"BalanceSheetNewTable0"})
return ratio
接著通過Tushare介面獲取相關財務指標以計算g:
def tus_growth(ts_code,period):
global growth
period_beg = str(int(period) -1) +"1231"
period = period+"1231"
ratio_end = pro.query('fina_indicator', ts_code=ts_code , period=period)
gl_table= pro.income(ts_code=ts_code, period=period, fields='ts_code,n_income', report_type=1)
ratio_beg = pro.query('fina_indicator', ts_code=ts_code , period=period_beg)
roe = float(ratio_end["roe"][0]) # ROE
re_beg =float(ratio_beg["retained_earnings"][0]) # Retain earning at the beg of period
re_end= float(ratio_end["retained_earnings"][0]) # Retain earning at the end of period
n_income = float(gl_table["n_income"][0]) # Net income
growth = roe *(1-(re_end-re_beg)/ n_income)
print("Growth rate of",ts_code,"is",growth,"%","with ROE:",roe, " net income:",n_income," at the period "
"end of", str(period)[:4])
return growth
計算得:
結合之前股利歷史狀況,1.3%的增速也可以理解,,可以視為幾乎沒有增速,凈利潤和后面的日期這些資訊就不展示了,
接下來,萬事具備只欠東風!
4:內在價值計算
呼叫之前的模塊,傳入市場代碼,證券代碼,起止日期,股利計算日期,大功告成!
if __name__ == '__main__':
market = input('Market Code') # 000001.SH
company = input('Security Code') # 600522.SH
start = input('Start Date(Dealing Day)')
end = input('End Date(Dealing Day)')
#Clac risk free , beta & E(r)
rf = float(risk_free()) # Calculate risk free rate
beta_Mr = data_input(company, market, start, end)
beta = beta_Mr[0] # Beat value
Mr = beta_Mr[1]
expt_r = rf + beta * (Mr - rf)
print("Output", company, "E(r):", expt_r, "%")
# calc dividend at beginning
div_table = craw_dividend(company)[0]
div_date = input("Input a complete financial period (year) for dividend")
div_date = str(div_date[:4])
row = div_table[(div_table["year"] == div_date)].index.tolist()
div_value = div_table.iloc[int(row[0])]["dividend"]
print("The dividend for {0}".format(div_date),"is:", div_value)
# calc g
div_date= int(div_date)-1 ## still need adjustment
tus_growth(company, str(div_date))
# Formular(Golden growth model)
price = div_value*(1+(growth/100)) / ((expt_r - (growth))/100)
print("The intrinsic value of", company,"is", price)
四、模型結果
該證券模型結果為:
筆者對該結果頗為滿意,筆者利用相對估值法計算也有著相似的價值,不管模型是不是真正估計到了這只明天盒子里薛定諤的貓,在筆者心里已經成功了,
五、寫在后面
程式可以個性化查看,但也可以批量跑,把Input的資訊變成串列便利即可把大A四千家公司統統擼一遍,
關于理想國:筆者所用公司是經過挑選的,換句話說只有符合條件的公司才能與我的世界契合,這基于以下幾點條件或假設:
1):該公司增長率極低,因此股利折現時g的威力對模型沒有什么影響;但對于一些高分紅,高ROE的公司來說,該模型或面臨失效;2):模型結果是該公司在筆者眼中的價值,該公司經營基本面穩定,且未來長期永續經營,筆者也進行長期投資;3):這家公司有足夠多的資料(至少近十年)供計算,對于一些上市時間較短的公司,模型有效性或將大打折扣;
我的世界也并非要一成不變的,現實情況是許多公司分紅都是不穩定或呈現周期性波動,這樣可以將過去幾年的股利均值視為進行計算,增長率g也并非永續且固定,多階段折現,采用更保守的方式折現都可以估計出一個區間,這個區間下限將是保守值,上線將是預警值,
最后,筆者認為決定一個公司股價高度的是市值而并非其內在價值,內在價值只能決定一個均值回歸時的中樞,以內在價值進行交易是不一定劃算的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298320.html
標籤:其他
