JerBouma / FinanceToolkit

Transparent and Efficient Financial Analysis
https://www.jeroenbouma.com/projects/financetoolkit
MIT License
2.94k stars 356 forks source link

[FR] Present value of growth opportunities #88

Closed giraffeingreen closed 10 months ago

giraffeingreen commented 10 months ago

What's the problem of not having this feature? No specific problem, just seems as a useful ratio to have.

Describe the solution you would like A simple PVGO ratio available for everyone.

Describe alternatives you've considered Custom Ratios

Additional information Wikipedia page: https://en.wikipedia.org/wiki/Present_value_of_growth_opportunities

JerBouma commented 10 months ago

Hi @giraffeingreen! Do you have any further examples from different sources? Just trying to see if I can expand it further with similar ratios of this type!

giraffeingreen commented 10 months ago

Nothing different than those mentioned in the Wikipedia page and Investopidia. I intend to explore various approaches to modeling the Present Value of Growth Opportunities (PVGO). Initially, I will utilize the formula $PVGO = \frac{Earnings \cdot Growth}{Cost of Capital}$, extracting implied growth. Subsequently, I will examine the correlation between implied growth and the actual market growth.

I'm working on my bachelor's degree, and any input or insights on this topic are greatly appreciated.

JerBouma commented 10 months ago

Got it, so how I would model this is do the following:

from financetoolkit import Toolkit

companies = Toolkit(
    tickers=['GOOGL', 'MSFT', 'AMZN'],
    api_key="FMP_KEY",
)

# Annual Historical Data
annual_historical_data = companies.get_historical_data(period='yearly')['Adj Close'][companies._tickers].T 

# Earnings per Share
earnings_per_share = companies.ratios.get_earnings_per_share()

# Weighted Average Cost of Capital
wacc = companies.models.get_weighted_average_cost_of_capital().loc[:, "Weighted Average Cost of Capital", :]

# Then calculate PVGO
pvgo = (annual_historical_data - earnings_per_share) / wacc

Which in this case would return:

2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023
AMZN 50.9611 87.7815 3066.14 297.01 224.219 -875.8 238.226 1275.86 579.098 -308.897 nan
GOOGL 89.6945 152.935 3223.96 329.264 200.815 -703.16 167.738 479.068 384.005 -335.092 nan
MSFT 104.93 301.379 4734.93 423.923 319.828 -1376.99 392.384 1044.26 997.574 -973.381 1221.17

Could you have a look if this makes sense for you? I can definitely add in this model but mostly looking if the current method is logical. See the following pages to see documentation about each component:

I also recommend to have a look at the WACC output without specifically selecting the final result (so companies.models.get_weighted_average_cost_of_capital())

There also seem to be multiple ways to do this calculation, e.g. https://corporatefinanceinstitute.com/resources/valuation/present-value-growth-opportunities-pvgo/ is using Cost of Equity which would net a different result.

image

Perhaps the biggest remark is that everything can already be calculated with the Finance Toolkit, it's up to you to see if this suffices.

giraffeingreen commented 10 months ago

Small correction:

# Then calculate PVGO
pvgo = (annual_historical_data - earnings_per_share) / wacc

Should be:

# Then calculate PVGO
pvgo = annual_historical_data - earnings_per_share / wacc

Alternative approach using market capitalization: pvgo = market_capitalization - earnings / wacc

in case this would save some time on calculations.

I prefer WACC over Cost of Equity.

giraffeingreen commented 10 months ago

I just noticed from the WACC documentation that you already have the Market Capitalization as Market Value of Equity.

JerBouma commented 10 months ago

Just checking, is there anything you need that is not available in the Finance Toolkit? I will probably add these metrics at some point in time.

giraffeingreen commented 10 months ago

Nothing else, thank you very much! Should I close the issue?

JerBouma commented 10 months ago

Let's keep it open, I'll close it once I've added these things in 👍🏼

giraffeingreen commented 10 months ago

How should I modify the below code to get the price daily but keep the wacc calculations yearly. Basically, I want basically to plot the PVGO daily and see how it changes around the 10k release date. Should I take a different approach?


from financetoolkit import Toolkit

companies = Toolkit(
    tickers=['GOOGL', 'MSFT', 'AMZN'],
    api_key="FMP_KEY",
)

# Annual Historical Data
annual_historical_data = companies.get_historical_data(period='yearly')['Adj Close'][companies._tickers].T 

# Earnings per Share
earnings_per_share = companies.ratios.get_earnings_per_share()

# Weighted Average Cost of Capital
wacc = companies.models.get_weighted_average_cost_of_capital().loc[:, "Weighted Average Cost of Capital", :]

# Then calculate PVGO
pvgo = (annual_historical_data - earnings_per_share) / wacc
JerBouma commented 10 months ago

You can not plot the PVGO on a daily basis as it depends on two ratios that require quarterly or yearly input. At best, you can see the quarterly results:

from financetoolkit import Toolkit

companies = Toolkit(
    tickers=['GOOGL', 'MSFT', 'AMZN'],
    api_key="FINANCIAL_MODELING_PREP_KEY",
    quarterly=True
)

# Annual Historical Data
quarterly_historical_data = companies.get_historical_data(period='quarterly')['Adj Close'][companies._tickers].T 

# Earnings per Share
earnings_per_share = companies.ratios.get_earnings_per_share()

# Weighted Average Cost of Capital
wacc = companies.models.get_weighted_average_cost_of_capital().loc[:, "Weighted Average Cost of Capital", :]

# Then calculate PVGO
pvgo = quarterly_historical_data - earnings_per_share / wacc

# Plot the Data
pvgo.T.plot(figsize=(10, 5), title="Present Value of Growth Opportunities (Quarterly)", grid=True, legend=True, ylabel="PVGO", xlabel="Date", colormap='tab20')

Which would return:

image

What you could do is overlay the stock price.

import matplotlib.pyplot as plt

daily_historical_data = companies.get_historical_data(period='daily')['Adj Close'][companies._tickers]

plt.style.use('seaborn-v0_8-deep')

figure, axes = plt.subplots(1, 1, figsize=(15, 5))

pvgo.loc['AMZN'].plot(ax=axes)

daily_historical_data['AMZN'].plot(ax=axes)

plt.legend(['PVGO', 'Price'])

plt.title("Present Value of Growth Opportunities (Quarterly) for Amazon")

plt.show()

image

giraffeingreen commented 10 months ago

Is it possible to calculate PVGO by taking the daily price and applying forward fill to the other two ratios before computation?

JerBouma commented 10 months ago

Yes, of course but I would seriously question what that statistic means. The further you are away from the financial report the metric is based on, the more noise the metric will have. For example, if news comes out that the revenue is going to exceed expectations, you will have a period in which the financial statement has not been released yet while the stock price has already gone up.

PVGO would in that case overreact given that daily_historical_data goes up whereas earnings_per_share lags behind until the financials are released.

giraffeingreen commented 10 months ago

And when the earnings are released you would have a correction, which would be an overreaction probably. I want to see if this is cyclical, and applicable to most of the stock market.

JerBouma commented 10 months ago

Got it, in any case this is how you could do it:


from financetoolkit import Toolkit

companies = Toolkit(
    tickers=['GOOGL', 'MSFT', 'AMZN'],
    api_key="FMP_KEY",
    quarterly=False
)

# Daily Historical Data
daily_historical_data = companies.get_historical_data(period='daily')['Adj Close'][companies._tickers]

# Earnings per Share
earnings_per_share = companies.ratios.get_earnings_per_share()

# Weighted Average Cost of Capital
wacc = companies.models.get_weighted_average_cost_of_capital().loc[:, "Weighted Average Cost of Capital", :]

# Then calculate Earning Wacc Ratio, transposed
earnings_wacc_ratio =  (earnings_per_share / wacc).T

# Convert index to Days
earnings_wacc_ratio.index = pd.PeriodIndex(earnings_wacc_ratio.index, freq='D')

# Merge the Datasets together
combined_dataset = pd.merge(daily_historical_data, earnings_wacc_ratio, left_index=True, right_index=True, how='left')

# Forward Fill the NaN values and drop the rest
combined_dataset =combined_dataset.ffill().dropna()

# Rename Columns
combined_dataset.columns = [f'{stock} Adj Close' for stock in companies._tickers] + [f'{stock} Earning Wacc Ratio' for stock in companies._tickers]

# Calculate PVGO
for ticker in companies._tickers:
    combined_dataset[f'{ticker} PVGO'] = combined_dataset[f'{ticker} Adj Close'] - combined_dataset[f'{ticker} Earning Wacc Ratio']

# Rounding
combined_dataset = combined_dataset.round(4)

# Plot the Data
combined_dataset.loc[:, [f'{stock} PVGO' for stock in companies._tickers]].plot(figsize=(10, 5), title="Present Value of Growth Opportunities (Daily)", grid=True, legend=True, ylabel="PVGO", xlabel="Date", colormap='tab20')

Which returns:

image

giraffeingreen commented 10 months ago

Negative PVGO, would mean that earnings/wacc > Market Cap, am I correct? Why is the plot going all the way to 2024?

JerBouma commented 10 months ago

Negative PVGO, would mean that earnings/wacc > Market Cap, am I correct? Why is the plot going all the way to 2024?

Not sure, take a look at the underlying formulas as there are multiple things at play here especially in the WACC calculation.

The plot goes all the way to 2024 because how I've programmed it here is that it forwards fill each Earnings/WACC ratio. Given that the daily returns go all the way to 2024, you also see this in the plot.

JerBouma commented 10 months ago

I have added this metric to the Finance Toolkit, see v1.7.4 here: https://github.com/JerBouma/FinanceToolkit/releases/tag/v1.7.4