Stock Valuation in Python: Phil Town Intrinsic Value

Automatically value a stock according to Phil Town’s investment methodology specified in his book Rule 1, using a python.

James Peter Webb
JPA-Quant-Articles
4 min readSep 19, 2021

--

A financial dashboard.
Photo by PhotoMIX Company from Pexels

In his New York Times best-seller Rule #1, Phil Town outlines his investment philosophy that follows a number of rules.

The four M’s

  • Meaning: to you
  • Moat: the ability to maintain competitiveness, consider ROIC, EPS, book value and both sales and cash growth
  • Management: good management qualities, CEO must show drive and shareholder return focus
  • Margin of Safety: the difference between intrinsic value, and desired purchase price. Town recommends an MoS of 50%

To calculate the Town’s desired purchase price, you require five numbers, current EPS, projected EPS growth rate, estimated future PE, and a minimum rate of return (which he recommends to be 15%). To obtain these numbers we will be using macrotrends and some lesser known yahoo finance related packages, that do not come pre-installed on most IDEs so first install and import them. It must be noted that this valuation method works best for large-cap, non-cyclical, value stocks.

>> in !pip install yahoofinancials
!pip install yahoo_fin
from yahoofinancials import YahooFinancials
import yahoo_fin.stock_info as si

The YahooFinancials package allows you to import the majority of data found on a companies yahoo finance page, by instantiating an object and calling different get methods, for example if you wanted the current EPS of a company (which we do), this could simply be done by calling get_earnings_per_share(). Throughout this article, we will be using Microsoft (MSFT) as an example.

>> in msft = YahooFinancials('MSFT)
>> eps_dict = msft.get_earnings_per_share()
>> out 8.05000036240706

You’ll notice that this provides us with true values, without formatting which is an added advantage of using these packages. Next we need EPS growth rate, there are many ways to get this, so feel free to implement your own, but to keep this as hands off as possible, I will be using the consensus of analysts projections of five-year annual growth rate.

>> in cagr = si.get_analysts_info('MSFT')['Growth Estimates']['MSFT'][4]>> out '15.25%'

Responses are formatted in dictionaries, hence nesting references. The final number required is estimated future PE. This can be obtained in two ways, and to remain as conservative and risk averse as possible, be sure to use the lower of the two. Either use the average of the last few years (I use 5), or twice the CAGR. Lets get the historical PEs first.

>> in pe_histo = pd.read_html(f'https://www.macrotrends.net/stocks/charts/MSFT/microsoft/pe-ratio')[0]
>> pe_histo.columns = pe_histo.columns.droplevel(0)
pe_histo.rename(columns={list(pe_histo)[0]:'Year', list(pe_histo)[3]:'PE'}, inplace=True)
pe_histo
>> out

This will return a dataframe containing quarterly PEs, nice. Lets now calculate the two PEs, and select the most conservative (Ensure to reformat the variable type of cagr, as it will initially be returned as a string with a ‘%’ sign).

>> in pe1 = float(200 * cagr)
>> pe2 = float(pe_histo['PE'].iloc[0:20].mean())
>> pe = min(pe1, pe2)
>> out 30.5

Now all our inputs are obtained, we need to forecast. First lets create a dictionary of years from now, and projected earnings, using current EPS as the first value.

>> in earnings_dict = {0:       round(stock_obj.get_earnings_per_share(),2)}
>> for i in range(1,10):
j = i - 1
earnings_dict[i] = round(earnings_dict[j]+(earnings_dict[j] * cagr),2)
>> out {0: 8.05,
1: 9.28,
2: 10.7,
3: 12.33,
4: 14.21,
5: 16.38,
6: 18.88,
7: 21.76,
8: 25.08,
9: 28.9}

Next lets iterate over the dictionary in reverse, and calculate fair price from the end of forecast, back to now.

>> in fair_price_dict = {9: earnings_dict[9]*pe}

>> for i in range(8,-1,-1):
j = i + 1
fair_price_dict[i] = fair_price_dict[j]/(1+0.15)
>> out {9: 881.4499999999999,
8: 766.4782608695652,
7: 666.5028355387525,
6: 579.5676830771761,
5: 503.9718983279792,
4: 438.2364333286776,
3: 381.07515941624143,
2: 331.36970384021,
1: 288.14756855670436,
0: 250.5631030927864}

Current fair values of the stock is found at 0, however, we don’t want to buy a firm at fair price, but rather we want to buy at a good price, a cheap price. This is where the margin of safety comes in.

>> in current_fair_price = round(fair_price_dict[0],2)
buyable_price = round((current_fair_price * 0.5), 2)
>> out 125.28

With current growth projection, a required rate of return of 15%, and a margin of safety of 50%, MSFT becomes attracted at or below $125.28.

>> in phil_town('MSFT', 0.15, 'microsoft', 0.5, True)>> out MSFT: Expensive
Target = <$125.28
Current = $305.22

On my github I have formalized a function to automate this, and chart both EPS and EPS growth.

--

--

James Peter Webb
JPA-Quant-Articles

Analyst at CryptoCompare, MSc FinTech and Investment, BSc Chemistry: Follow my projects in quantitative analysis and Christian finance.