File size: 16,749 Bytes
c11f284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# Importing required modules
import pandas as pd
import numpy as np
import numpy as np
import plotly.express as px

# To extract and parse fundamental data like beta and growth estimates from finviz website
import requests
from bs4 import BeautifulSoup as bs

# For parsing financial statements data from financialmodelingprep api
from urllib.request import urlopen
import json

# For Gradio App
import gradio as gr

import os
# uncomment and set API Key in the environment variable below
# or you can choose to set it using any other method you know
#os.environ['FMP_API_KEY'] = "your_api_key"

# read the environment variable to use in API requests later
apiKey = os.environ['FMP_API_KEY']


############################################################################################################
###### GET DATA FROM FINANCIAL MODELING PREP
############################################################################################################

# Financialmodelingprep api url
base_url = "https://financialmodelingprep.com/api/v3/"

def get_jsonparsed_data(url):
    response = urlopen(url)
    data = response.read().decode("utf-8")
    return json.loads(data)

# get financial statements using financial modelling prep API
def get_financial_statements(ticker):
    # quarterly cash flow statements for calculating latest trailing twelve months (TTM) free cash flow
    columns_drop = ['acceptedDate', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate', 'depreciationAndAmortization']
    q_cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?period=quarter' + '&apikey=' + apiKey))
    q_cash_flow_statement = q_cash_flow_statement.set_index('date').drop(columns_drop, axis=1).iloc[:4] # extract for last 4 quarters
    latest_year = int(q_cash_flow_statement.iloc[0]['calendarYear'])

    # annual cash flow statements
    cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?apikey=' + apiKey))
    cash_flow_statement = cash_flow_statement.set_index('date').drop(columns_drop, axis=1)
    
    # combine annual and latest TTM cash flow statements
    ttm_cash_flow_statement = q_cash_flow_statement.sum() # sum up last 4 quarters to get TTM cash flow
    cash_flow_statement = cash_flow_statement[::-1].append(ttm_cash_flow_statement.rename('TTM')).drop(['netIncome'], axis=1)
    final_cash_flow_statement = cash_flow_statement[::-1] # reverse list to show most recent ones first
    
    # quarterly balance sheet statements
    columns_drop = ['acceptedDate', 'calendarYear', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate']
    q_balance_statement = pd.DataFrame(get_jsonparsed_data(base_url+'balance-sheet-statement/' + ticker + '?' + '&apikey=' + apiKey))
    q_balance_statement = q_balance_statement.set_index('date').drop(columns_drop, axis=1)
    q_balance_statement = q_balance_statement.apply(pd.to_numeric, errors='coerce')
        
    return q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year
 
 
# check stability of cash flows
def plot_cash_flow(ticker, cash_flow_statement):       
    # DCF model works best only if the free cash flows are POSITIVE, STABLE and STEADILY INCREASING. 
    # So let's plot the graph and verify if this is the case.
    fig_cash_flow = px.bar(cash_flow_statement , y='freeCashFlow', title=ticker + ' Free Cash Flows')
    fig_cash_flow.update_xaxes(type='category', tickangle=270, title='Date')
    fig_cash_flow.update_yaxes(title='Free Cash Flows')
    #fig_cash_flow.show()
    return fig_cash_flow


# get ttm cash flow, most recent total debt and cash & short term investment data from statements
def get_statements_data(final_cash_flow_statement, q_balance_statement):
    cash_flow = final_cash_flow_statement.iloc[0]['freeCashFlow'] # ttm cash flow
    total_debt = q_balance_statement.iloc[0]['totalDebt'] 
    cash_and_ST_investments = q_balance_statement.iloc[0]['cashAndShortTermInvestments']
    return cash_flow, total_debt, cash_and_ST_investments


############################################################################################################
###### GET DATA FROM FINVIZ WEBSITE 
############################################################################################################

# Price, EPS next Y/5Y, Beta, Number of Shares Outstanding
# 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.

# List of data we want to extract from Finviz Table
# Price is the current stock price
# EPS next Y is the estimated earnings growth for next year
# 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)
# Beta captures the volatility of the stock, used for estimating discount rate later
# Shs Outstand is the number of shares present in the market
metric = ['Price', 'EPS next Y', 'EPS next 5Y', 'Beta', 'Shs Outstand']

def fundamental_metric(soup, metric):
    # the table which stores the data in Finviz has html table attribute class of 'snapshot-td2'
    return soup.find_all(text = metric)[-1].find_next(class_='snapshot-td2').text

# get above metrics from finviz and store as a dict 
def get_finviz_data(ticker):
    try:
        url = ("http://finviz.com/quote.ashx?t=" + ticker.lower())
        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)
        dict_finviz = {}        
        for m in metric:   
            dict_finviz[m] = fundamental_metric(soup,m)
        for key, value in dict_finviz.items():
            # replace percentages
            if (value[-1]=='%'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])
            # billion
            if (value[-1]=='B'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])*1000000000  
            # million
            if (value[-1]=='M'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])*1000000
            try:
                dict_finviz[key] = float(dict_finviz[key])
            except:
                pass 
    except Exception as e:
        print (e)
        print ('Not successful parsing ' + ticker + ' data.')        
    return dict_finviz


def parse_finviz_dict(finviz_dict):
    EPS_growth_5Y = finviz_dict['EPS next 5Y']
    # sometimes EPS next 5Y is empty and shows as a '-' string, in this case use EPS next Y
    if isinstance(EPS_growth_5Y, str):
        if not EPS_growth_5Y.isdigit():
            EPS_growth_5Y = finviz_dict['EPS next Y']
    EPS_growth_6Y_to_10Y = EPS_growth_5Y/2  # Half the previous growth rate, conservative estimate
    #EPS_growth_11Y_to_20Y  = np.minimum(EPS_growth_6Y_to_10Y, 4)  # Slightly higher than long term inflation rate, conservative estimate
    long_term_growth_rate = np.minimum(EPS_growth_6Y_to_10Y, 3)  # Slightly higher than long term inflation rate, conservative estimate
    shares_outstanding = finviz_dict['Shs Outstand']
    beta = finviz_dict['Beta']
    current_price = finviz_dict['Price']
    
    return EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price


## Estimate Discount Rate from Beta
def estimate_discount_rate(beta):
    # Beta shows the volatility of the stock, 
    # the higher the beta, we want to be more conservative by increasing the discount rate also.
    discount_rate = 7
    if(beta<0.80):
        discount_rate = 5
    elif(beta>=0.80 and beta<1):
        discount_rate = 6
    elif(beta>=1 and beta<1.1):
        discount_rate = 6.5
    elif(beta>=1.1 and beta<1.2):
        discount_rate = 7
    elif(beta>=1.2 and beta<1.3):
        discount_rate = 7.5
    elif(beta>=1.3 and beta<1.4):
        discount_rate = 8
    elif(beta>=1.4 and beta<1.6):
        discount_rate = 8.5
    elif(beta>=1.61):
        discount_rate = 9   

    return discount_rate


############################################################################################################
## Calculate Intrinsic Value
############################################################################################################

# 1. First Project Cash Flows from Year 1 to Year 10 using Present (TTM) Free Cash Flow
# 2. Discount the Cash Flows to Present Value
# 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/)
# 4. Add the Cash Flows and the Terminal Value Up
# 5. Then Account for the Cash + Short Term Investments and Subtract Total Debt
# 6. Divide by Total Number of Shares Outstanding

def calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments, 
                                  EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
                                  shares_outstanding, discount_rate, current_price):   
    
    # Convert all percentages to decmials
    EPS_growth_5Y_d = EPS_growth_5Y/100
    EPS_growth_6Y_to_10Y_d = EPS_growth_6Y_to_10Y/100
    long_term_growth_rate_d = long_term_growth_rate/100
    discount_rate_d = discount_rate/100
    # print("Discounted Cash Flows\n")
    
    # Lists of projected cash flows from year 1 to year 20
    cash_flow_list = []
    cash_flow_discounted_list = []
    year_list = []
        
    # Years 1 to 5
    for year in range(1, 6):
        year_list.append(year + latest_year)
        cash_flow*=(1 + EPS_growth_5Y_d)        
        cash_flow_list.append(cash_flow)
        cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
        cash_flow_discounted_list.append(cash_flow_discounted)
        # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
    
    # Years 6 to 10
    for year in range(6, 11):
        year_list.append(year + latest_year)
        cash_flow*=(1 + EPS_growth_6Y_to_10Y_d)
        cash_flow_list.append(cash_flow)
        cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
        cash_flow_discounted_list.append(cash_flow_discounted)
        # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
    
    # Store all forecasted cash flows in dataframe
    forecast_cash_flows_df = pd.DataFrame.from_dict({'Year': year_list, 'Cash Flow': cash_flow_list, 'Discounted Cash Flow': cash_flow_discounted_list})
    forecast_cash_flows_df = forecast_cash_flows_df.set_index('Year')
    
    # Growth in Perpuity Approach    
    cashflow_10Y = cash_flow_discounted_list[-1]
    # Formula to Calculate: https://corporatefinanceinstitute.com/resources/financial-modeling/dcf-terminal-value-formula/
    terminal_value = cashflow_10Y*(1+long_term_growth_rate_d)/(discount_rate_d-long_term_growth_rate_d)
    
    # Yay finally
    intrinsic_value = (sum(cash_flow_discounted_list) + terminal_value - total_debt + cash_and_ST_investments)/shares_outstanding
    margin_of_safety = (1-current_price/intrinsic_value)*100
    
    return forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety


# Plot forecasted cash flows from years 1 to 10, as well as the discounted cash flows
def plot_forecasted_cash_flows(ticker, forecast_cash_flows_df):

    fig_cash_forecast = px.bar(forecast_cash_flows_df, barmode='group', title=ticker + ' Projected Free Cash Flows')
    fig_cash_forecast.update_xaxes(type='category', tickangle=270)
    fig_cash_forecast.update_xaxes(tickangle=270, title='Forecasted Year')
    fig_cash_forecast.update_yaxes(title='Free Cash Flows')
    # fig_cash_forecast.show()
    
    return fig_cash_forecast


# chain all the steps from the functions above together
def run_all_steps(ticker):
    q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year = get_financial_statements(ticker)

    fig_cash_flow = plot_cash_flow(ticker, cash_flow_statement)

    cash_flow, total_debt, cash_and_ST_investments = get_statements_data(final_cash_flow_statement, q_balance_statement)

    finviz_dict = get_finviz_data(ticker)

    EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price = parse_finviz_dict(finviz_dict)

    discount_rate = estimate_discount_rate(beta)

    forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety = calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments, 
                                      EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
                                      shares_outstanding, discount_rate, current_price)

    fig_cash_forecast = plot_forecasted_cash_flows(ticker, forecast_cash_flows_df)
    
    return q_cash_flow_statement.reset_index(), final_cash_flow_statement.reset_index(), q_balance_statement.reset_index(), fig_cash_flow, \
        str(EPS_growth_5Y) + '%' , str(EPS_growth_6Y_to_10Y) + '%', str(long_term_growth_rate) + '%', \
        beta, shares_outstanding, current_price, \
        discount_rate, forecast_cash_flows_df.reset_index(), terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety
      

# Gradio App and UI
with gr.Blocks() as app:
    with gr.Row():
        gr.HTML("<h1>Bohmian's Stock Intrinsic Value Calculator</h1>")

    with gr.Row():        
        ticker = gr.Textbox("AAPL", label='Enter stock ticker to calculate its intrinsic value e.g. "AAPL"')
        btn = gr.Button("Calculate Intrinsic Value")
        
    # Show intrinsic value calculation results
    with gr.Row():
        gr.HTML("<h2>Calculated Intrinsic Value</h2>")
        
    with gr.Row():
        intrinsic_value = gr.Text(label="Intrinsic Value")
        current_price = gr.Text(label="Actual Stock Price")
        margin_of_safety = gr.Text(label="Margin of Safety")
    
    # Show metrics obtained and estimated from FinViz website that were essential for calculations
    with gr.Row():
        gr.HTML("<h2>Metrics Obtained (and Estimated) from FinViz Website</h2>")
    with gr.Row():
        gr.HTML("<h3>https://finviz.com/</h3>")
        
    with gr.Row():
            EPS_growth_5Y = gr.Text(label="EPS Next 5Y (Estimated EPS growth for next 5 years)")
            EPS_growth_6Y_to_10Y = gr.Text(label="EPS growth for 6th to 10th year (estimated as half of above)")
            long_term_growth_rate = gr.Text(label="Long Term Growth Rate (estimated as half of above or 3%, whichever is lower)")  
    
    with gr.Row():
        beta = gr.Text(label="Beta (Measures volatility of stock)")
        discount_rate = gr.Text(label="Discount Rate (estimated from beta)") 
        shares_outstanding = gr.Text(label="Shares Outstanding")             

       
    # Show detailed actual historical financial statements
    with gr.Row():
        gr.HTML("<h2>Actual Historical Financial Statements Data</h2>")
    with gr.Row():
        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>")
    
    with gr.Row():        
        fig_cash_flow = gr.Plot(label="Historical Cash Flows")
        
    with gr.Row():   
        q_cash_flow_statement = gr.DataFrame(label="Last 4 Quarterly Cash Flow Statements")
        
    with gr.Row():   
        final_cash_flow_statement = gr.DataFrame(label="TTM + Annual Cash Flow Statements")
    
    with gr.Row():   
        q_balance_statement = gr.DataFrame(label="Quarterly Balance Statements")
    
    
    # Show forecasted cash flows and terminal value
    with gr.Row():
        gr.HTML("<h2>Forecasted Cash Flows for Next 10 Years</h2>")
        
    with gr.Row():
        fig_cash_forecast = gr.Plot(label="Forecasted Cash Flows")
        forecast_cash_flows_df = gr.DataFrame(label="Forecasted Cash Flows")

    with gr.Row():
        terminal_value = gr.Text(label="Terminal Value (after 10th year)")
        

    btn.click(fn=run_all_steps, inputs=[ticker], 
              outputs=[q_cash_flow_statement, final_cash_flow_statement, q_balance_statement, fig_cash_flow, \
        EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price, \
        discount_rate, forecast_cash_flows_df, terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety])

app.launch()