파이썬으로 DCF 구현하기 2: 추정 및 WACC값 구하기
프로젝트 구조
코드 구현의 순서는 다음과 같습니다.
1. 요약 재무제표 크롤링
- FCFF 도출에 필요한 항목들만 추려내서 데이터 프레임으로 정리하기
- 각 항목들의 추정치 도출하기 (ex 매출액의 연평균성장률, 평균 매출원가율)
2. 정제된 재무제표를 바탕으로 미래 FCFF 추정하기
- 1번에서 구한 추정치를 바탕으로 FCFF추정하기
- 사용자의 시나리오에 따라 FCFF 추정하기
3. WACC 값 도출을 위한 크롤링
- Rf, Rm, B 등등 크롤링해오기
4. 주가가치 산출하기
- 앞선 과정으로 얻어진 값들로 회사가치, 주가가치 계산하기
- wacc, 영구성장률 변화에 따른 민감도 차이 보여주기
5. 시각화
- 민감도별 시각화하기
오늘은 2,3 을 진행해보겠습니다.
1번을 할때와 다르게 조금 생각해보셔야할게 있습니다. 일단 2번에서 사용된 수치들이 그렇게 믿을만하지 못하다는 것입니다. 제무재표를 fn가이드에서 크롤링하다보니 3년치밖에 가져오지 못했습니다. 3년은 뭔가 방향성을 보기에는 애매한 시간이라서 수치들이 믿음직하지는 못하지만 뭐 어쩌겠습니까...
그럴 줄 알고 투자자가 직접 추정치를 수정할 수 있게 코드를 짜놨습니다. 기본 옵션은 3년 동안의 방향성을 따라가는 것이지만 투자자가 산업, 기업등을 분석하고 각종 이슈들을 추정치에 담아 추정할 수 있게 해주자는 것이 저의 생각이었습니다.
예를들어 x기업의 경우 매출이 올해부터 급상승하고 온라인화로 비용이 절감되었다고 생각하면 그것을 매출액, 매출원가 추정에 반영하는 것이죠
그리고 WACC구하기.. 아마 어떤 분은 어처구니가 없다고 느끼실 수도 있지만 위와같은 기준을을 이용했으니 참고바랍니다.. 그리고 Beta의 경우 원래는 구하는 법이 살짝 까다롭지만, 이 프로그램의 목적이 간소화, 자동화이니 야후 파이낸스에서 몸소 계산해준 수치를 가져오고 있습니다.
오늘은 2, 3번 코드를 공유하며 마무리해보겠습니다. 얘가 왜 이렇게 한건가 질문해주시거나 잘못된 것에 대해 지적해주시면 정말정말 큰 도움이 될 것 같습니다.
아직 메인함수가 없어서 바로 코드 복붙해보시면 아무것도 안나올 수 있습니다. 다른 시리즈로 코드를 완성하고 실행해보세요
class step2:
import requests
from bs4 import BeautifulSoup
import re
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
def __init__(self,table,tax):
self._table = table
self._tax = tax
self._cagr = 0
self._cogs_avg = 0
self._sgna_avg = 0
self._capex_ratio = 0
self._dep_ratio = 0
self._amo_ratio = 0
self._NWC_ratio = 0
def ratios(self):
self._cagr = round((self._table.iloc[0][2]/self._table.iloc[0][0])**(1/2) - 1,2)
for i in range(3):
self._cogs_avg += (self._table.iloc[1][i]/self._table.iloc[0][i])/len(self._table.iloc[0])
self._sgna_avg += (self._table.iloc[2][i]/self._table.iloc[0][i])/len(self._table.iloc[0])
self._capex_ratio += (self._table.iloc[6][i]/self._table.iloc[0][i])/len(self._table.iloc[0])
self._dep_ratio += (self._table.iloc[4][i]/self._table.iloc[0][i])/len(self._table.iloc[0])
self._amo_ratio += (self._table.iloc[5][i]/self._table.iloc[0][i])/len(self._table.iloc[0])
self._NWC_ratio += (self._table.iloc[9][i]/self._table.iloc[3][i])/len(self._table.iloc[3])
def Assume(self):
print("각 항목들이 다음의 수치를 기반으로 추정됩니다.\n")
print("1. {0:15} :{1}%".format('매출-연평균 매출성장률',round(self._cagr*100,2)))
print("2. {0:15} :{1}%".format('매출원가-평균 매출원가율', round(self._cogs_avg*100,2)))
print("3. {0:15} :{1}%".format('판매관리비-평균 판관비율',round(self._sgna_avg*100,2)))
print("4. {0:15} :{1}%".format('자본적지출-매출대비비중',round(self._capex_ratio*100,2)))
print("5. {0:15} :{1}%".format('유형자산상각비-매출대비비중',round(self._dep_ratio*100,2)))
print("6. {0:15} :{1}%".format('무형자산상각비-매출대비비중',round(self._amo_ratio*100,2)))
print("7. {0:15} :{1}%".format('순운전자본-영업이익대비비중',round(self._NWC_ratio*100,2)))
while (1):
try:
q1 = input("\n신규추정을 할 항목의 번호를 입력하세요(0 입력시 기본수치로 진행): ")
if q1 == '0':
break
elif q1 == '1':
self._cagr = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '2':
self._cogs_avg = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '3':
self._sgna_avg = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '4':
self._capex_ratio = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '5':
self._dep_ratio = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '6':
self._amo_ratio = float(input("신규추정치를 입력하세요(%): "))/100.0
elif q1 == '7':
self._NWC_ratio = float(input("신규추정치를 입력하세요(%): "))/100.0
else:
raise ValueError
except:
print("Erorr!: 잘못된 값을 입력했습니다. 0~7의 숫자만 입력하세요")
print("\n__________________________________________________________________________________________")
print("신규 추정치\n")
print("1. {0:15} :{1}%".format('매출-연평균 매출성장률',round(self._cagr*100,2)))
print("2. {0:15} :{1}%".format('매출원가-평균 매출원가율', round(self._cogs_avg*100,2)))
print("3. {0:15} :{1}%".format('판매관리비-평균 판관비율',round(self._sgna_avg*100,2)))
print("4. {0:15} :{1}%".format('자본적지출-매출대비비중',round(self._capex_ratio*100,2)))
print("5. {0:15} :{1}%".format('유형자산상각비-매출대비비중',round(self._dep_ratio*100,2)))
print("6. {0:15} :{1}%".format('무형자산상각비-매출대비비중',round(self._amo_ratio*100,2)))
print("7. {0:15} :{1}%".format('순운전자본-영업이익대비비중',round(self._NWC_ratio*100,2)))
print("__________________________________________________________________________________________")
def forecast(self):
F_sales =[]
F_cogs = []
F_sgna = []
F_op =[]
F_capex =[]
F_dep = []
F_amo = []
F_dnwc = []
F_FCFF = []
F_NWC = []
for i in range(5):
if i == 0:
F_sales.append(round(self._table.iloc[0][2]*(1+self._cagr),1))
F_cogs.append(round(F_sales[i]*self._cogs_avg,1))
F_sgna.append(round(F_sales[i]*self._sgna_avg,1))
F_op.append(round(F_sales[i]-F_cogs[i]-F_sgna[i])*(1-self._tax))
F_capex.append(round(F_sales[i]*self._capex_ratio,1))
F_dep.append(round(F_sales[i]*self._dep_ratio,1))
F_amo.append(round(F_sales[i]*self._amo_ratio,1))
F_NWC.append(round(F_op[i]*self._NWC_ratio,1))
F_dnwc.append(round(F_NWC[i]-self._table.iloc[9][2],1))
F_FCFF.append(round(F_op[i]+F_dep[i]+F_amo[i]-F_capex[i]-F_dnwc[i],1))
else:
F_sales.append(round(F_sales[i-1]*(1+self._cagr),1))
F_cogs.append(round(F_sales[i]*self._cogs_avg,1))
F_sgna.append(round(F_sales[i]*self._sgna_avg,1))
F_op.append(round(F_sales[i]-F_cogs[i]-F_sgna[i])*(1-self._tax))
F_capex.append(round(F_sales[i]*self._capex_ratio,1))
F_dep.append(round(F_sales[i]*self._dep_ratio,1))
F_amo.append(round(F_sales[i]*self._amo_ratio,1))
F_NWC.append(round(F_op[i]*self._NWC_ratio,1))
F_dnwc.append(round(F_NWC[i]-F_NWC[i-1],1))
F_FCFF.append(round(F_op[i]+F_dep[i]+F_amo[i]-F_capex[i]-F_dnwc[i],1))
dFIS = {'매출액': F_sales,'매출원가': F_cogs,'판관비': F_sgna
,'영업이익': F_op,'+유형자산상각비':F_dep,'+무형자산상각비': F_amo,
'-Capex': F_capex,'-dNwc': F_dnwc, 'FCFF': F_FCFF}
fyr1 = (datetime.datetime.today()).strftime("%Y")+'F'
fyr2 = (datetime.datetime.today() + relativedelta(years = 1)).strftime("%Y")+'F'
fyr3 = (datetime.datetime.today() + relativedelta(years = 2)).strftime("%Y")+'F'
fyr4 = (datetime.datetime.today() + relativedelta(years = 3)).strftime("%Y")+'F'
fyr5 = (datetime.datetime.today() + relativedelta(years = 4)).strftime("%Y")+'F'
ISFt = pd.DataFrame(dFIS, index = [fyr1, fyr2, fyr3, fyr4, fyr5])
FIS=ISFt.transpose()
print("추정 5개년 FCFF(단위: 억)")
display(FIS)
return FIS
class WACC:
import requests
from bs4 import BeautifulSoup
import re
print("\n__________________________________________________________________________________________")
print("WACC는 가중평균 자본비용으로 미래 가치들에 대한 할인율로 이용된다.")
def __init__(self, company, BS, tax):
self._company = company
self._BS = BS
self._tax = tax
self._Rf = 0
self._Rm = 0
self._beta = 0
self._ke = 0
self._Re = 0
def risk_free_rate(self):
source_url = 'http://index.go.kr/potal/main/EachDtlPageDetail.do?idx_cd=1073'
tables = pd.read_html(source_url, encoding='utf-8')
rf_r = tables[1]
b5y = list(rf_r.iloc[2,5:9])
b5y.append(rf_r.iloc[2,-1])
for i in range(len(b5y)):
self._Rf += b5y[i]/len(b5y)
self._Rf = round(self._Rf/100,4)
print("Rf : 무위험 수익률")
print("한국 국고채 5년 평균 수익률: {}%\n".format(str(round(self._Rf*100,3))))
return self._Rf
def market_risk_premium(self):
url = 'https://finance.yahoo.com/quote/%5EKS11/history?period1=1466294400&period2=1624060800&interval=1mo&filter=history&frequency=1mo&includeAdjustedClose=true'
response = requests.get(url)
if response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
today_r = soup.select_one('#Col1-1-HistoricalDataTable-Proxy > section > div.Pb\(10px\).Ovx\(a\).W\(100\%\) > table > tbody > tr:nth-child(1) > td:nth-child(6) > span')
old_r = soup.select_one('#Col1-1-HistoricalDataTable-Proxy > section > div.Pb\(10px\).Ovx\(a\).W\(100\%\) > table > tbody > tr:nth-child(60) > td:nth-child(6) > span')
else :
print(response.status_code)
today_t = today_r.get_text()
old_t = old_r.get_text()
ft = re.compile('\d+,\d+(?=.)')
today = ft.match(today_t)
today = int(today.group().replace(',',''))
old = ft.match(old_t)
old = int(old.group().replace(',',''))
self._Rm = round((today/old)**(1/4)-1,3)
print("Rm : 시장 수익률")
print("KOSPI 5년 평균 수익률: {}%\n".format(str(round(self._Rm*100,3))))
return self._Rm
def beta(self):
ft = re.compile('\w+')
comp = ft.match(self._company).group()
url = 'https://finance.yahoo.com/quote/017810.KS?p=017810.KS&.tsrc=fin-srch'.format(comp)
response = requests.get(url)
if response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
beta_r = soup.select_one('#quote-summary > div.D\(ib\).W\(1\/2\).Bxz\(bb\).Pstart\(12px\).Va\(t\).ie-7_D\(i\).ie-7_Pos\(a\).smartphone_D\(b\).smartphone_W\(100\%\).smartphone_Pstart\(0px\).smartphone_BdB.smartphone_Bdc\(\$seperatorColor\) > table > tbody > tr:nth-child(2) > td.Ta\(end\).Fw\(600\).Lh\(14px\) > span')
else :
print(response.status_code)
self._beta = beta_r.get_text()
self._beta = int(self._beta.replace('.',''))/100
print("Beta : 회사주가와 시장지수의 상관계수")
print("Leverd Beta: {}\n".format(self._beta))
print(">>>>Re(자기자본비용) = Rf + Beta(Rm-Rf)\n\n ")
return self._beta
def cost_debt(self):
source_url = 'https://www.kisrating.com/ratingsStatistics/statics_spread.do'
tables = pd.read_html(source_url, encoding='utf-8')
self._ke_r = tables[0]
self._ke_r.index = list(self._ke_r.iloc[:,0])
self._ke_r = self._ke_r.drop(['구분','3월','6월','9월','1년','1년6월','2년','3년'],axis = 1)
url = 'https://comp.fnguide.com/SVO2/asp/SVD_Main.asp?pGB=1&gicode={}&cID=&MenuYn=Y&ReportGB=&NewMenuID=101&stkGb=701'.format(self._company)
response = requests.get(url)
if response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
credit_r = soup.select_one('#svdMainGrid7 > table > tbody > tr > td.clf.c')
else :
print(response.status_code)
credit_t = credit_r.get_text()
ft = re.compile('\w+[+|-]?')
credit = ft.match(credit_t).group()
self._ke = self._ke_r.loc[credit, '5년']/100
print("Ke(타인자본비용)")
print("{0} 등급 회사채 수익률: {1}%\n".format(credit, str(round(self._ke*100,3))))
return self._ke
def wacc(self):
equity_ratio = round(self._BS.iloc[8,4]/self._BS.iloc[0,4],4)
debt_ratio = 1 - equity_ratio
self._Re = self._Rf+self._beta*(self._Rm-self._Rf)
wacc = self._ke*(1-self._tax)*debt_ratio + self._Re*equity_ratio
print("WACC = Re*자본비율 + Ke(1-tax)*부채비율")
print("WACC(가중평균자본비용): {}%".format(str(round(wacc*100,2))))
return wacc