투자
파이썬으로 DCF 구현하기 3: 주가 산출
하드리아누스
2021. 6. 29. 11:35
프로젝트 구조
코드 구현의 순서는 다음과 같습니다.
1. 요약 재무제표 크롤링
- FCFF 도출에 필요한 항목들만 추려내서 데이터 프레임으로 정리하기
- 각 항목들의 추정치 도출하기 (ex 매출액의 연평균성장률, 평균 매출원가율)
2. 정제된 재무제표를 바탕으로 미래 FCFF 추정하기
- 1번에서 구한 추정치를 바탕으로 FCFF추정하기
- 사용자의 시나리오에 따라 FCFF 추정하기
3. WACC 값 도출을 위한 크롤링
- Rf, Rm, B 등등 크롤링해오기
4. 주가가치 산출하기
- 앞선 과정으로 얻어진 값들로 회사가치, 주가가치 계산하기
- wacc, 영구성장률 변화에 따른 민감도 차이 보여주기
5. 시각화
- 민감도별 시각화하기
오늘은 4번을 진행해볼텐데 여기서 독특한 점은 영구성장률을 투자자가 설정해야한다는 점과 비영업용 자산, 이자발생 부채등을 크롤링한다는 점인데, 이런 자산, 부채들의 경우 사실 정확하게 크롤링하는 것이 어려웠습니다. 많은 회사들이 정보를 제공하지 않아서 오류가나기도합니다.
오늘은 4번 코드를 공유하며 마무리해보겠습니다. 얘가 왜 이렇게 한건가 질문해주시거나 잘못된 것에 대해 지적해주시면 정말정말 큰 도움이 될 것 같습니다.
아직 메인함수가 없어서 바로 코드 복붙해보시면 아무것도 안나올 수 있습니다. 다른 시리즈로 코드를 완성하고 실행해보세요.
import matplotlib.pyplot as plt
import numpy as np
class DCF:
def __init__(self, forecast, wacc, company):
self._forecast = forecast
self._wacc = wacc
self._company = company
self._stockvalue = 0
self._pv_FCFF = 0
self._TV = 0
self._pv_TV = 0
self._net_NOA = 0
self._Pg = 0
self._TV_t = 0
self._PVTV_t = 0
self._pvfcff_t = 0
self._stock_value_t = 0
def pv_FCFF(self):
sen_t = []
index = []
for i in range(5):
self._pv_FCFF += self._forecast.loc['FCFF'][i]/((1+self._wacc)**(i+1))
for j in range(-2,3):
t_pv_FCFF = 0
tmp_wacc = (self._wacc + (j/100))*100
index.append(str(round(tmp_wacc,2))+'%')
for i in range(len(self._forecast.loc['FCFF'])):
t_pv_FCFF += self._forecast.loc['FCFF'][i]/((1+self._wacc+(j/100))**(i+1))
sen_t.append(round(t_pv_FCFF,1))
self._pvfcff_t = pd.DataFrame(sen_t, index = index)
self._pvfcff_t = self._pvfcff_t.transpose()
self._pvfcff_t.index = ['PV of FCFF']
print("__________________________________________________________________________________________")
print("\n\nPV of FCFF 민감도 분석 테이블")
display(self._pvfcff_t)
print("WACC : {}".format(str(round(self._wacc*100,2))+'%'))
return self._pv_FCFF
def TV(self):
t_data = []
columns = []
index = []
print("\n\n영구성장률은 추정 마지막 년도 이후에 회사의 성장률입니다. 보통 0% ~ 2%가 적절한 값입니다.")
try:
self._Pg = float(input("영구성장률을 입력하세요(%): "))/100
if self._wacc-self._Pg <= 0:
raise ValueError
except ValueError:
print("Erorr!: 잘못된 값을 입력했습니다. 숫자를 입력하세요.")
self._Pg = float(input("영구성장률을 입력하세요(%): "))/100
try:
self._TV = self._forecast.loc['FCFF'][4]*(1+self._Pg)/(self._wacc-self._Pg)
except ZeroDivisionError:
print("Erorr!: wacc와 Pg의 차이가 거의 나지 않습니다. Pg를 다시 설정해주세요.")
self._Pg = float(input("영구성장률을 입력하세요(%): "))/100
for i1 in range(-2,3):
r_data = []
pg = self._Pg + (i1/200)
index.append(str(round(100*pg,1))+'%')
columns.append(str(round(100*(self._wacc+(i1/100)),2))+'%')
for i2 in range(-2,3,1):
wacc = self._wacc + (i2/100)
TV = (self._forecast.loc['FCFF'][4]*(1+pg))/round(wacc-pg,2)
r_data.append(round(TV,2))
t_data.append(r_data)
self._pv_TV = self._TV/((1+self._wacc)**5)
self._TV_t = pd.DataFrame(t_data, index=index, columns = columns)
self._PVTV_t = pd.DataFrame(index=index, columns = columns)
for j1 in range(5):
for j2 in range(-2,3):
wacc = self._wacc + (j2/100)
self._PVTV_t.iloc[j1][j2+2] = round(self._TV_t.iloc[j1][j2+2]/((1+wacc)**5),2)
print("__________________________________________________________________________________________")
print("\nTV 및 PV of TV 민감도 분석 테이블")
print("WACC : {}% 영구성장률: {}%".format(str(round(self._wacc*100,2)),str(round(self._Pg*100,2))))
display(self._TV_t)
display(self._PVTV_t)
def NOA_IBD(self): #except로 없으면 경고문 발사하기
url = 'https://comp.fnguide.com/SVO2/asp/SVD_Finance.asp?pGB=1&gicode={}&cID=&MenuYn=Y&ReportGB=&NewMenuID=103&stkGb=701'.format(self._company)
response = requests.get(url)
if response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
IBD_L_r = soup.select_one('#divDaechaY > table > tbody > tr:nth-child(46) > td.r.cle')
IBD_S_r = soup.select_one('#divDaechaY > table > tbody > tr:nth-child(32) > td.r.cle')
Cash_r = soup.select_one('#divDaechaY > table > tbody > tr:nth-child(12) > td.r.cle')
NPC_r = soup.select_one('#divDaechaY > table > tbody > tr:nth-child(66) > td.r.cle')
NOA_r = soup.select_one('#divDaechaY > table > tbody > tr:nth-child(19) > td.r.cle')
else :
print(response.status_code)
ft = re.compile('\d+\,?\d+')
IBD_L_t = IBD_L_r.get_text()
IBD_L = ft.match(IBD_L_t)
IBD_L = int(IBD_L.group().replace(',',''))
IBD_S_t = IBD_S_r.get_text()
IBD_S = ft.match(IBD_S_t)
IBD_S = int(IBD_S.group().replace(',',''))
Cash_t = Cash_r.get_text()
Cash = ft.match(Cash_t)
Cash = int(Cash.group().replace(',',''))
NPC_t = NPC_r.get_text()
NPC = ft.match(NPC_t)
NPC = int(NPC.group().replace(',',''))
NOA_t = NOA_r.get_text()
NOA = ft.match(NOA_t)
NOA = int(NOA.group().replace(',',''))
self._net_NOA = NOA + Cash - NPC - (IBD_L+IBD_S)
def EV(self):
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')
stock_r = soup.select_one('#highlight_D_A > table > tbody > tr:nth-child(24) > td:nth-child(4)')
name_r = soup.select_one('#giName')
else :
print(response.status_code)
ft = re.compile('\d+\,?\d+')
stock_t = stock_r.get_text()
stock = ft.match(stock_t)
stock = int(stock.group().replace(',',''))
ft2 = re.compile('\w+')
name_t = name_r.get_text()
name = ft2.match(name_t).group()
Enterprise_value = self._pv_FCFF + self._pv_TV
Equity_value = Enterprise_value + self._net_NOA
stock_value = int((Equity_value*100000000)/(stock*1000))
Enterprise_value_t = pd.DataFrame(index = list(self._PVTV_t.index),columns = list(self._PVTV_t.columns))
Equity_value_t = pd.DataFrame(index = list(self._PVTV_t.index),columns = list(self._PVTV_t.columns))
self._stock_value_t = pd.DataFrame(index = list(self._PVTV_t.index),columns = list(self._PVTV_t.columns))
for i1 in range(5):
for i2 in range(5):
Enterprise_value_t.iloc[i1][i2] = self._PVTV_t.iloc[i1][i2] + self._pvfcff_t.iloc[0][i2]
Equity_value_t.iloc[i1][i2] = Enterprise_value_t.iloc[i1][i2] + self._net_NOA
self._stock_value_t.iloc[i1][i2] = round(((Enterprise_value_t.iloc[i1][i2] + self._net_NOA)*100000000)/(stock*1000))
self._stockvalue = self._stock_value_t.iloc[2][2]
print("__________________________________________________________________________________________")
print("회사 가치(단위: 억)")
display(Enterprise_value_t)
print("회사의 자산 가치(단위: 억)")
display(Equity_value_t)
print("회사의 주가 가치(단위: 원)")
display(self._stock_value_t)
print("DCF평가법을 통한 {}의 주가는 {}원 입니다.\n".format(name, self._stockvalue))
return self._stockvalue, self._stock_value_t