bohmian commited on
Commit
c11f284
1 Parent(s): 158c936

first commit

Browse files
[Reference and Explanation Notebook] Calculate Intrinsic Value of a Stock.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
app.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Importing required modules
2
+ import pandas as pd
3
+ import numpy as np
4
+ import numpy as np
5
+ import plotly.express as px
6
+
7
+ # To extract and parse fundamental data like beta and growth estimates from finviz website
8
+ import requests
9
+ from bs4 import BeautifulSoup as bs
10
+
11
+ # For parsing financial statements data from financialmodelingprep api
12
+ from urllib.request import urlopen
13
+ import json
14
+
15
+ # For Gradio App
16
+ import gradio as gr
17
+
18
+ import os
19
+ # uncomment and set API Key in the environment variable below
20
+ # or you can choose to set it using any other method you know
21
+ #os.environ['FMP_API_KEY'] = "your_api_key"
22
+
23
+ # read the environment variable to use in API requests later
24
+ apiKey = os.environ['FMP_API_KEY']
25
+
26
+
27
+ ############################################################################################################
28
+ ###### GET DATA FROM FINANCIAL MODELING PREP
29
+ ############################################################################################################
30
+
31
+ # Financialmodelingprep api url
32
+ base_url = "https://financialmodelingprep.com/api/v3/"
33
+
34
+ def get_jsonparsed_data(url):
35
+ response = urlopen(url)
36
+ data = response.read().decode("utf-8")
37
+ return json.loads(data)
38
+
39
+ # get financial statements using financial modelling prep API
40
+ def get_financial_statements(ticker):
41
+ # quarterly cash flow statements for calculating latest trailing twelve months (TTM) free cash flow
42
+ columns_drop = ['acceptedDate', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate', 'depreciationAndAmortization']
43
+ q_cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?period=quarter' + '&apikey=' + apiKey))
44
+ q_cash_flow_statement = q_cash_flow_statement.set_index('date').drop(columns_drop, axis=1).iloc[:4] # extract for last 4 quarters
45
+ latest_year = int(q_cash_flow_statement.iloc[0]['calendarYear'])
46
+
47
+ # annual cash flow statements
48
+ cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?apikey=' + apiKey))
49
+ cash_flow_statement = cash_flow_statement.set_index('date').drop(columns_drop, axis=1)
50
+
51
+ # combine annual and latest TTM cash flow statements
52
+ ttm_cash_flow_statement = q_cash_flow_statement.sum() # sum up last 4 quarters to get TTM cash flow
53
+ cash_flow_statement = cash_flow_statement[::-1].append(ttm_cash_flow_statement.rename('TTM')).drop(['netIncome'], axis=1)
54
+ final_cash_flow_statement = cash_flow_statement[::-1] # reverse list to show most recent ones first
55
+
56
+ # quarterly balance sheet statements
57
+ columns_drop = ['acceptedDate', 'calendarYear', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate']
58
+ q_balance_statement = pd.DataFrame(get_jsonparsed_data(base_url+'balance-sheet-statement/' + ticker + '?' + '&apikey=' + apiKey))
59
+ q_balance_statement = q_balance_statement.set_index('date').drop(columns_drop, axis=1)
60
+ q_balance_statement = q_balance_statement.apply(pd.to_numeric, errors='coerce')
61
+
62
+ return q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year
63
+
64
+
65
+ # check stability of cash flows
66
+ def plot_cash_flow(ticker, cash_flow_statement):
67
+ # DCF model works best only if the free cash flows are POSITIVE, STABLE and STEADILY INCREASING.
68
+ # So let's plot the graph and verify if this is the case.
69
+ fig_cash_flow = px.bar(cash_flow_statement , y='freeCashFlow', title=ticker + ' Free Cash Flows')
70
+ fig_cash_flow.update_xaxes(type='category', tickangle=270, title='Date')
71
+ fig_cash_flow.update_yaxes(title='Free Cash Flows')
72
+ #fig_cash_flow.show()
73
+ return fig_cash_flow
74
+
75
+
76
+ # get ttm cash flow, most recent total debt and cash & short term investment data from statements
77
+ def get_statements_data(final_cash_flow_statement, q_balance_statement):
78
+ cash_flow = final_cash_flow_statement.iloc[0]['freeCashFlow'] # ttm cash flow
79
+ total_debt = q_balance_statement.iloc[0]['totalDebt']
80
+ cash_and_ST_investments = q_balance_statement.iloc[0]['cashAndShortTermInvestments']
81
+ return cash_flow, total_debt, cash_and_ST_investments
82
+
83
+
84
+ ############################################################################################################
85
+ ###### GET DATA FROM FINVIZ WEBSITE
86
+ ############################################################################################################
87
+
88
+ # Price, EPS next Y/5Y, Beta, Number of Shares Outstanding
89
+ # Extract (using requests.get) and Parse (using Beautiful Soup) data from Finviz table in the Finviz website (see screenshot above), needed to calculate intrinsic value of stock.
90
+
91
+ # List of data we want to extract from Finviz Table
92
+ # Price is the current stock price
93
+ # EPS next Y is the estimated earnings growth for next year
94
+ # EPS next 5Y is the estimated earnings growth for next 5 years (if this is not present on finviz, we will use EPS next Y instead)
95
+ # Beta captures the volatility of the stock, used for estimating discount rate later
96
+ # Shs Outstand is the number of shares present in the market
97
+ metric = ['Price', 'EPS next Y', 'EPS next 5Y', 'Beta', 'Shs Outstand']
98
+
99
+ def fundamental_metric(soup, metric):
100
+ # the table which stores the data in Finviz has html table attribute class of 'snapshot-td2'
101
+ return soup.find_all(text = metric)[-1].find_next(class_='snapshot-td2').text
102
+
103
+ # get above metrics from finviz and store as a dict
104
+ def get_finviz_data(ticker):
105
+ try:
106
+ url = ("http://finviz.com/quote.ashx?t=" + ticker.lower())
107
+ soup = bs(requests.get(url,headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0'}).content)
108
+ dict_finviz = {}
109
+ for m in metric:
110
+ dict_finviz[m] = fundamental_metric(soup,m)
111
+ for key, value in dict_finviz.items():
112
+ # replace percentages
113
+ if (value[-1]=='%'):
114
+ dict_finviz[key] = value[:-1]
115
+ dict_finviz[key] = float(dict_finviz[key])
116
+ # billion
117
+ if (value[-1]=='B'):
118
+ dict_finviz[key] = value[:-1]
119
+ dict_finviz[key] = float(dict_finviz[key])*1000000000
120
+ # million
121
+ if (value[-1]=='M'):
122
+ dict_finviz[key] = value[:-1]
123
+ dict_finviz[key] = float(dict_finviz[key])*1000000
124
+ try:
125
+ dict_finviz[key] = float(dict_finviz[key])
126
+ except:
127
+ pass
128
+ except Exception as e:
129
+ print (e)
130
+ print ('Not successful parsing ' + ticker + ' data.')
131
+ return dict_finviz
132
+
133
+
134
+ def parse_finviz_dict(finviz_dict):
135
+ EPS_growth_5Y = finviz_dict['EPS next 5Y']
136
+ # sometimes EPS next 5Y is empty and shows as a '-' string, in this case use EPS next Y
137
+ if isinstance(EPS_growth_5Y, str):
138
+ if not EPS_growth_5Y.isdigit():
139
+ EPS_growth_5Y = finviz_dict['EPS next Y']
140
+ EPS_growth_6Y_to_10Y = EPS_growth_5Y/2 # Half the previous growth rate, conservative estimate
141
+ #EPS_growth_11Y_to_20Y = np.minimum(EPS_growth_6Y_to_10Y, 4) # Slightly higher than long term inflation rate, conservative estimate
142
+ long_term_growth_rate = np.minimum(EPS_growth_6Y_to_10Y, 3) # Slightly higher than long term inflation rate, conservative estimate
143
+ shares_outstanding = finviz_dict['Shs Outstand']
144
+ beta = finviz_dict['Beta']
145
+ current_price = finviz_dict['Price']
146
+
147
+ return EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price
148
+
149
+
150
+ ## Estimate Discount Rate from Beta
151
+ def estimate_discount_rate(beta):
152
+ # Beta shows the volatility of the stock,
153
+ # the higher the beta, we want to be more conservative by increasing the discount rate also.
154
+ discount_rate = 7
155
+ if(beta<0.80):
156
+ discount_rate = 5
157
+ elif(beta>=0.80 and beta<1):
158
+ discount_rate = 6
159
+ elif(beta>=1 and beta<1.1):
160
+ discount_rate = 6.5
161
+ elif(beta>=1.1 and beta<1.2):
162
+ discount_rate = 7
163
+ elif(beta>=1.2 and beta<1.3):
164
+ discount_rate = 7.5
165
+ elif(beta>=1.3 and beta<1.4):
166
+ discount_rate = 8
167
+ elif(beta>=1.4 and beta<1.6):
168
+ discount_rate = 8.5
169
+ elif(beta>=1.61):
170
+ discount_rate = 9
171
+
172
+ return discount_rate
173
+
174
+
175
+ ############################################################################################################
176
+ ## Calculate Intrinsic Value
177
+ ############################################################################################################
178
+
179
+ # 1. First Project Cash Flows from Year 1 to Year 10 using Present (TTM) Free Cash Flow
180
+ # 2. Discount the Cash Flows to Present Value
181
+ # 3. Calculate the Terminal Value after Year 10 (Discounted to Present Value) Assuming the Company will Grow at a Constant Steady Rate Forever (https://corporatefinanceinstitute.com/resources/financial-modeling/dcf-terminal-value-formula/)
182
+ # 4. Add the Cash Flows and the Terminal Value Up
183
+ # 5. Then Account for the Cash + Short Term Investments and Subtract Total Debt
184
+ # 6. Divide by Total Number of Shares Outstanding
185
+
186
+ def calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments,
187
+ EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
188
+ shares_outstanding, discount_rate, current_price):
189
+
190
+ # Convert all percentages to decmials
191
+ EPS_growth_5Y_d = EPS_growth_5Y/100
192
+ EPS_growth_6Y_to_10Y_d = EPS_growth_6Y_to_10Y/100
193
+ long_term_growth_rate_d = long_term_growth_rate/100
194
+ discount_rate_d = discount_rate/100
195
+ # print("Discounted Cash Flows\n")
196
+
197
+ # Lists of projected cash flows from year 1 to year 20
198
+ cash_flow_list = []
199
+ cash_flow_discounted_list = []
200
+ year_list = []
201
+
202
+ # Years 1 to 5
203
+ for year in range(1, 6):
204
+ year_list.append(year + latest_year)
205
+ cash_flow*=(1 + EPS_growth_5Y_d)
206
+ cash_flow_list.append(cash_flow)
207
+ cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
208
+ cash_flow_discounted_list.append(cash_flow_discounted)
209
+ # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
210
+
211
+ # Years 6 to 10
212
+ for year in range(6, 11):
213
+ year_list.append(year + latest_year)
214
+ cash_flow*=(1 + EPS_growth_6Y_to_10Y_d)
215
+ cash_flow_list.append(cash_flow)
216
+ cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
217
+ cash_flow_discounted_list.append(cash_flow_discounted)
218
+ # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
219
+
220
+ # Store all forecasted cash flows in dataframe
221
+ forecast_cash_flows_df = pd.DataFrame.from_dict({'Year': year_list, 'Cash Flow': cash_flow_list, 'Discounted Cash Flow': cash_flow_discounted_list})
222
+ forecast_cash_flows_df = forecast_cash_flows_df.set_index('Year')
223
+
224
+ # Growth in Perpuity Approach
225
+ cashflow_10Y = cash_flow_discounted_list[-1]
226
+ # Formula to Calculate: https://corporatefinanceinstitute.com/resources/financial-modeling/dcf-terminal-value-formula/
227
+ terminal_value = cashflow_10Y*(1+long_term_growth_rate_d)/(discount_rate_d-long_term_growth_rate_d)
228
+
229
+ # Yay finally
230
+ intrinsic_value = (sum(cash_flow_discounted_list) + terminal_value - total_debt + cash_and_ST_investments)/shares_outstanding
231
+ margin_of_safety = (1-current_price/intrinsic_value)*100
232
+
233
+ return forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety
234
+
235
+
236
+ # Plot forecasted cash flows from years 1 to 10, as well as the discounted cash flows
237
+ def plot_forecasted_cash_flows(ticker, forecast_cash_flows_df):
238
+
239
+ fig_cash_forecast = px.bar(forecast_cash_flows_df, barmode='group', title=ticker + ' Projected Free Cash Flows')
240
+ fig_cash_forecast.update_xaxes(type='category', tickangle=270)
241
+ fig_cash_forecast.update_xaxes(tickangle=270, title='Forecasted Year')
242
+ fig_cash_forecast.update_yaxes(title='Free Cash Flows')
243
+ # fig_cash_forecast.show()
244
+
245
+ return fig_cash_forecast
246
+
247
+
248
+ # chain all the steps from the functions above together
249
+ def run_all_steps(ticker):
250
+ q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year = get_financial_statements(ticker)
251
+
252
+ fig_cash_flow = plot_cash_flow(ticker, cash_flow_statement)
253
+
254
+ cash_flow, total_debt, cash_and_ST_investments = get_statements_data(final_cash_flow_statement, q_balance_statement)
255
+
256
+ finviz_dict = get_finviz_data(ticker)
257
+
258
+ EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price = parse_finviz_dict(finviz_dict)
259
+
260
+ discount_rate = estimate_discount_rate(beta)
261
+
262
+ forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety = calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments,
263
+ EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
264
+ shares_outstanding, discount_rate, current_price)
265
+
266
+ fig_cash_forecast = plot_forecasted_cash_flows(ticker, forecast_cash_flows_df)
267
+
268
+ return q_cash_flow_statement.reset_index(), final_cash_flow_statement.reset_index(), q_balance_statement.reset_index(), fig_cash_flow, \
269
+ str(EPS_growth_5Y) + '%' , str(EPS_growth_6Y_to_10Y) + '%', str(long_term_growth_rate) + '%', \
270
+ beta, shares_outstanding, current_price, \
271
+ discount_rate, forecast_cash_flows_df.reset_index(), terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety
272
+
273
+
274
+ # Gradio App and UI
275
+ with gr.Blocks() as app:
276
+ with gr.Row():
277
+ gr.HTML("<h1>Bohmian's Stock Intrinsic Value Calculator</h1>")
278
+
279
+ with gr.Row():
280
+ ticker = gr.Textbox("AAPL", label='Enter stock ticker to calculate its intrinsic value e.g. "AAPL"')
281
+ btn = gr.Button("Calculate Intrinsic Value")
282
+
283
+ # Show intrinsic value calculation results
284
+ with gr.Row():
285
+ gr.HTML("<h2>Calculated Intrinsic Value</h2>")
286
+
287
+ with gr.Row():
288
+ intrinsic_value = gr.Text(label="Intrinsic Value")
289
+ current_price = gr.Text(label="Actual Stock Price")
290
+ margin_of_safety = gr.Text(label="Margin of Safety")
291
+
292
+ # Show metrics obtained and estimated from FinViz website that were essential for calculations
293
+ with gr.Row():
294
+ gr.HTML("<h2>Metrics Obtained (and Estimated) from FinViz Website</h2>")
295
+ with gr.Row():
296
+ gr.HTML("<h3>https://finviz.com/</h3>")
297
+
298
+ with gr.Row():
299
+ EPS_growth_5Y = gr.Text(label="EPS Next 5Y (Estimated EPS growth for next 5 years)")
300
+ EPS_growth_6Y_to_10Y = gr.Text(label="EPS growth for 6th to 10th year (estimated as half of above)")
301
+ long_term_growth_rate = gr.Text(label="Long Term Growth Rate (estimated as half of above or 3%, whichever is lower)")
302
+
303
+ with gr.Row():
304
+ beta = gr.Text(label="Beta (Measures volatility of stock)")
305
+ discount_rate = gr.Text(label="Discount Rate (estimated from beta)")
306
+ shares_outstanding = gr.Text(label="Shares Outstanding")
307
+
308
+
309
+ # Show detailed actual historical financial statements
310
+ with gr.Row():
311
+ gr.HTML("<h2>Actual Historical Financial Statements Data</h2>")
312
+ with gr.Row():
313
+ gr.HTML("<h3>IMPORTANT NOTE: DCF model works best only if the free cash flows are POSITIVE, STABLE and STEADILY INCREASING. Check if this is the case.</h3>")
314
+
315
+ with gr.Row():
316
+ fig_cash_flow = gr.Plot(label="Historical Cash Flows")
317
+
318
+ with gr.Row():
319
+ q_cash_flow_statement = gr.DataFrame(label="Last 4 Quarterly Cash Flow Statements")
320
+
321
+ with gr.Row():
322
+ final_cash_flow_statement = gr.DataFrame(label="TTM + Annual Cash Flow Statements")
323
+
324
+ with gr.Row():
325
+ q_balance_statement = gr.DataFrame(label="Quarterly Balance Statements")
326
+
327
+
328
+ # Show forecasted cash flows and terminal value
329
+ with gr.Row():
330
+ gr.HTML("<h2>Forecasted Cash Flows for Next 10 Years</h2>")
331
+
332
+ with gr.Row():
333
+ fig_cash_forecast = gr.Plot(label="Forecasted Cash Flows")
334
+ forecast_cash_flows_df = gr.DataFrame(label="Forecasted Cash Flows")
335
+
336
+ with gr.Row():
337
+ terminal_value = gr.Text(label="Terminal Value (after 10th year)")
338
+
339
+
340
+ btn.click(fn=run_all_steps, inputs=[ticker],
341
+ outputs=[q_cash_flow_statement, final_cash_flow_statement, q_balance_statement, fig_cash_flow, \
342
+ EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price, \
343
+ discount_rate, forecast_cash_flows_df, terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety])
344
+
345
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ pandas
2
+ plotly
3
+ bs4