Add warnings based on Sean Seah book using MarketWatch source data

This commit is contained in:
didier
2021-01-06 23:10:01 +00:00
parent 5f57594137
commit 8a00b2d6c3
4 changed files with 181 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ import argparse
import pandas as pd
from bs4 import BeautifulSoup
import requests
import numpy as np
from stock_market_helper_funcs import *
# ---------------------------------------------------- INCOME ----------------------------------------------------
@@ -408,3 +409,140 @@ def sec_fillings(l_args, s_ticker):
except:
print("ERROR!\n")
return
# ---------------------------------------------------- Sean Seah Warnings ----------------------------------------------------
def sean_seah_warnings(l_args, s_ticker):
parser = argparse.ArgumentParser(prog='warnings',
description="""Sean Seah warnings. Check: Consistent historical earnings per share;
Consistently high return on equity; Consistently high return on assets; 5x Net
Income > Long-Term Debt; and Interest coverage ratio more than 3. [Source: Market Watch BS]""")
parser.add_argument('-d', "--debug", action="store_true", default=False, dest="b_debug", help='Show data used to calculate warnings')
try:
(ns_parser, l_unknown_args) = parser.parse_known_args(l_args)
if l_unknown_args:
print(f"The following args couldn't be interpreted: {l_unknown_args}")
# From INCOME STATEMENT, get: 'EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA'
url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/income"
text_soup_financials = BeautifulSoup(requests.get(url_financials).text,"lxml")
# Define financials columns
a_financials_header = list()
for financials_header in text_soup_financials.findAll('th', {'class': 'overflow__heading'}):
a_financials_header.append(financials_header.text.strip('\n').split('\n')[0])
df_financials = pd.DataFrame(columns=a_financials_header[0:-1])
# Add financials values
soup_financials = text_soup_financials.findAll('tr', {'class': 'table__row '})
soup_financials += text_soup_financials.findAll('tr', {'class': 'table__row is-highlighted'})
for financials_info in soup_financials:
a_financials_info = financials_info.text.split('\n')
l_financials = [a_financials_info[2]]
l_financials.extend(a_financials_info[5:-2])
# Append data values to financials
df_financials.loc[len(df_financials.index)] = l_financials
# Set item name as index
df_financials = df_financials.set_index('Item')
df_sean_seah = df_financials.loc[['EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA']]
# From BALANCE SHEET, get: 'Liabilities & Shareholders\' Equity', 'Long-Term Debt'
url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/balance-sheet"
text_soup_financials = BeautifulSoup(requests.get(url_financials).text,"lxml")
# Define financials columns
a_financials_header = list()
for financials_header in text_soup_financials.findAll('th', {'class': 'overflow__heading'}):
a_financials_header.append(financials_header.text.strip('\n').split('\n')[0])
s_header_end_trend = "5-year trend"
df_financials = pd.DataFrame(columns=a_financials_header[0:a_financials_header.index(s_header_end_trend)])
# Add financials values
soup_financials = text_soup_financials.findAll('tr', {'class': 'table__row '})
soup_financials += text_soup_financials.findAll('tr', {'class': 'table__row is-highlighted'})
for financials_info in soup_financials:
a_financials_info = financials_info.text.split('\n')
l_financials = [a_financials_info[2]]
l_financials.extend(a_financials_info[5:-2])
# Append data values to financials
df_financials.loc[len(df_financials.index)] = l_financials
# Set item name as index
df_financials = df_financials.set_index('Item')
# Create dataframe to compute meaningful metrics from sean seah book
df_sean_seah = df_sean_seah.append(df_financials.loc[['Total Shareholders\' Equity', 'Liabilities & Shareholders\' Equity', 'Long-Term Debt']])
# Clean these metrics by parsing their values to float
df_sean_seah = df_sean_seah.applymap(lambda x: clean_data_values_to_float(x))
# Add additional necessary metrics
series = df_sean_seah.loc['Net Income']/df_sean_seah.loc['Total Shareholders\' Equity']
series.name = "ROE"
df_sean_seah = df_sean_seah.append(series)
series = df_sean_seah.loc['EBITDA']/df_sean_seah.loc['Interest Expense']
series.name = "Interest Coverage Ratio"
df_sean_seah = df_sean_seah.append(series)
series = df_sean_seah.loc['Net Income']/df_sean_seah.loc['Liabilities & Shareholders\' Equity']
series.name = "ROA"
df_sean_seah = df_sean_seah.append(series)
print(df_sean_seah.applymap(lambda x: int_or_round_float(x)).to_string())
n_warnings = 0
print("\nWARNINGS:")
if np.any(df_sean_seah.loc['EPS (Basic)'].diff().dropna().values < 0):
print("NO consistent historical earnings per share")
n_warnings += 1
if ns_parser.b_debug:
sa_eps = np.array2string(df_sean_seah.loc['EPS (Basic)'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" EPS: {sa_eps}")
sa_growth = np.array2string(df_sean_seah.loc['EPS (Basic)'].diff().dropna().values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" Growth: {sa_growth} < 0")
if np.any(df_sean_seah.loc['ROE'].values < 0.15):
print("NOT consistently high return on equity")
n_warnings += 1
if ns_parser.b_debug:
sa_roe = np.array2string(df_sean_seah.loc['ROE'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" ROE: {sa_roe} < 0.15")
if np.any(df_sean_seah.loc['ROA'].values < 0.07):
print("NOT consistently high return on assets")
n_warnings += 1
if ns_parser.b_debug:
sa_roa = np.array2string(df_sean_seah.loc['ROA'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" ROA: {sa_roa} < 0.07")
if np.any(df_sean_seah.loc['Long-Term Debt'].values > 5*df_sean_seah.loc['Net Income'].values):
print("5x Net Income < Long-Term Debt")
n_warnings += 1
if ns_parser.b_debug:
sa_net_income = np.array2string(df_sean_seah.loc['Net Income'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" NET Income: {sa_net_income}")
sa_5_long_term_debt = np.array2string(5*df_sean_seah.loc['Long-Term Debt'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" lower than 5x Long-Term Debt: {sa_5_long_term_debt}")
if np.any(df_sean_seah.loc['Interest Coverage Ratio'].values < 3):
print("Interest coverage ratio less than 3")
n_warnings += 1
if ns_parser.b_debug:
sa_interest_coverage_ratio = np.array2string(100*df_sean_seah.loc['Interest Coverage Ratio'].values, formatter={'float_kind': lambda x: int_or_round_float(x)})
print(f" Interest Coverage Ratio: {sa_interest_coverage_ratio} < 3")
if n_warnings == 0:
print("None. Good stonk")
print("")
except:
print("ERROR!\n")
return

View File

@@ -29,6 +29,7 @@ def print_fundamental_analysis(s_ticker, s_start, s_interval):
print("\nFundamental Analysis:") # https://github.com/JerBouma/FundamentalAnalysis
print(" info provides information on main key metrics of company")
print(" warnings company warnings according to Sean Seah based on Market Watch data")
print(" help show this fundamental analysis menu again")
print(" q quit this menu, and shows back to main menu")
print(" quit quit to abandon program")
@@ -113,6 +114,9 @@ def fa_menu(fa_parser, s_ticker, s_start, s_interval):
if ns_known_args.fa == 'info':
info(l_args, s_ticker)
if ns_known_args.fa == 'warnings':
mw_bs.sean_seah_warnings(l_args, s_ticker)
elif ns_known_args.fa == 'help':
print_fundamental_analysis(s_ticker, s_start, s_interval)

View File

@@ -61,7 +61,7 @@ def main():
# Add list of arguments that the fundamental analysis parser accepts
fa_parser = argparse.ArgumentParser(prog='fundamental_analysis', add_help=False)
fa_parser.add_argument('fa', choices=['info', 'help', 'q', 'quit',
fa_parser.add_argument('fa', choices=['info', 'warnings', 'help', 'q', 'quit',
'overview', 'key', 'income', 'balance', 'cash', 'earnings', # AV
'profile', 'rating', 'quote', 'enterprise', 'dcf', # FMP
'inc', 'bal', 'cashf', 'metrics', 'ratios', 'growth', # FMP

View File

@@ -2,6 +2,7 @@
from pytz import timezone
from holidays import US as holidaysUS
from datetime import datetime, time as Time
import sys
import matplotlib
import matplotlib.pyplot as plt
@@ -152,3 +153,40 @@ def long_number_format(num):
else:
return '%.3f%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude])
return num
# -----------------------------------------------------------------------------------------------------------------------
def clean_data_values_to_float(val):
# Remove parenthesis if they exist
if val.startswith('('):
val = val[1:]
if val.endswith(')'):
val = val[:-1]
if val == '-':
val = '0'
# Convert percentage to decimal
if val.endswith('%'):
val = float(val[:-1])/100.0
# Convert from billions
elif val.endswith('B'):
val = float(val[:-1])*1_000_000_000
# Convert from millions
elif val.endswith('M'):
val = float(val[:-1])*1_000_000
# Convert from thousands
elif val.endswith('K'):
val = float(val[:-1])*1_000
else:
val = float(val)
return val
# -----------------------------------------------------------------------------------------------------------------------
def int_or_round_float(x):
if (x - int(x) < -sys.float_info.epsilon) or (x - int(x) > sys.float_info.epsilon):
return ' ' + str(round(x, 2))
else:
return ' ' + str(int(x))