Stock analysis takes a lot of time to filter and find the right one for a long-term investment. So I thought I could speed up the process by calculating a few basic values and write them all into one report file. Now I'd like to simplify everything as I'm sure there are a lot of redundant and repeating parts in it.
My code:
#!/usr/bin/python3
import locale
from time import strftime
print("-------------------------")
print(" Stock analysis ")
print("-------------------------")
# Declare base variables
locale.setlocale(locale.LC_ALL, '')
date = strftime("%Y-%m-%d")
time = strftime(" %H:%M:%S")
partners = "none"
trends = "none"
assets_increase = "y"
liabilities_increase = "n"
income_increase = "y"
age = "y"
forecast = "y"
commodity_reliance = "y"
# Start the dialogue
print("Let's start with some basic values...")
print("You can get the required information using Onvista or Yahoo Finance")
print("\n\nPlease note: This program uses the american number writing style. If you want to write decimal numbers, "
"please use a point instead of a comma to separate the digits (e.g. 1.1 instead of 1,1).\n\n")
name = input("Name of the company: ")
file = open("%s-report.txt" % name, "a")
wkn = input("WKN: ")
symbol = input("Symbol: ")
isin = input("ISIN: ")
sector = input("Sector: ")
# Check initial numbers
while True:
try:
current_price = float(input("Current price: "))
eps = float(input("EPS: "))
pe = float(input("PE: "))
market_cap = float(input("Market capitalization: "))
break
except ValueError:
print("please enter only numbers without comma and use . for decimals (e.g. 5.55 instead of 5,55")
continue
# Check next variables with failsafe so the program doesn't crash when the user enters wrong values
def check_partners_trends():
# Check Partners
global partners, trends
while True:
partner_check = input("Does it have big partners? (y/n): ")
if not partner_check.isalpha():
print("Please only enter y or n")
continue
if partner_check == "y":
partners = input("Who? ")
break
elif partner_check == "n":
partners = "none"
break
else:
print("Please only enter y or n")
continue
# Check Trend
while True:
trend_check = input("Is it participating in any current trends? (y/n): ")
if not trend_check.isalpha():
print("Please only enter y or n")
continue
if trend_check == "y":
trends = input("Which? ")
break
elif trend_check == "n":
trends = "none"
break
else:
print("Please only enter y or n")
continue
return partners, trends
check_partners_trends()
# Write to the report file
file.write("Report for company: " + name)
file.write("\nWKN: %s\tSymbol: %s\nISIN: %s\tSector: %s" % (wkn, symbol, isin, sector))
file.write("\n\nEvaluated: %s\nAt: %s\n\n\n" % (date, time)) # first \n for new line, second \n for one blank line
file.write(name + " is currently trading at: " + locale.currency(current_price, grouping=True))
file.write("\nEPS: %s\n\t--> The higher the better\nP/E: %s\nMarket capitalization: %s" % (eps, pe, locale.currency(market_cap, grouping=True)))
file.write("\n\nIt has the following partners: %s\nAnd is participating in the trend: %s" % (partners, trends))
print("\n__________")
print("Income Statement Analysis")
print("__________\n")
# Check income numbers
while True:
try:
total_revenue = float(input("Total Revenue: "))
gross_profit = float(input("Gross profit: "))
operating_expenses = float(input("Operating expenses: "))
cost_of_revenue = float(input("Cost of revenue: "))
net_income = float(input("Net income: "))
ebit = float(input("EBIT: "))
ebitda = float(input("EBITDA: "))
break
except ValueError:
print("please enter only numbers without comma and use . for decimals (e.g. 5.55 instead of 5,55")
continue
income_red_flags = 0
### Start writing to the report ###
file.write("\n\n\n_____________\n\nIncome Statement Analysis\n_____________\n")
file.write(
"\nTotal revenue: %s\nGross profit: %s\nOperating expenses: %s\nNet income: %s\nCost of revenue: %s\nEBIT: %s\n --> analyzes the performance of core operations\nEBITDA: %s\n --> earnings before interest, taxes, depreciation and amortization --> analyzes performance and projects earnings potential" % (
locale.currency(total_revenue, grouping=True), locale.currency(gross_profit, grouping=True),
locale.currency(operating_expenses, grouping=True), locale.currency(net_income, grouping=True),
locale.currency(cost_of_revenue, grouping=True), locale.currency(ebit, grouping=True),
locale.currency(ebitda, grouping=True)))
def analyze_income():
"""Analyze some of the given values and write them to the report"""
global income_red_flags, gross_profit, total_revenue, operating_expenses, cost_of_revenue
gross_margin = '{0:.2f}%'.format((gross_profit / total_revenue * 100))
# Return this value as percentage
operating_income = gross_profit - operating_expenses
file.write("\n\n\n[-->] Analyzing income...\n")
file.write(
"\nGross margin: {}\n --> Portion of each dollar of revenue that the company retains as profit (35% = 0,35 cent/dollar)\n".format(
gross_margin))
if operating_income < 0:
income_red_flags += 1
file.write(
"\n[!] Operating expenses are negative: %s\n --> Company is generating a loss." % locale.currency(
operating_income, grouping=True))
# company is generating loss
if operating_expenses > total_revenue:
income_red_flags += 1
file.write(
"\n[!] Operating expenses are higher than the revenue --> The company is spending more money than it is receiving.")
# company is spending more than it's receiving
if cost_of_revenue > gross_profit:
income_red_flags += 1
file.write("\n[!] Cost of revenue is higher than gross profits --> The product costs more than it pays.")
# the product costs more than it gives you
file.write("\n[!] Income red flags: %s" % income_red_flags)
return gross_margin, operating_income, income_red_flags
analyze_income()
print("\n__________")
print("Balance Sheet Analysis")
print("__________\n")
# Check Balance numbers
while True:
try:
# Can be liquidated within 1 year
total_assets = float(input("Total assets: "))
current_assets = float(input("Current Assets: "))
cash = float(input("Cash and cash equivalents: "))
inventory = float(input("Inventory: "))
# Total non-current assets -> can't be liquidated within 1 year
net_ppe = float(input("Net PPE: "))
# Check how that property is divided
depreciation = float(input("Depreciation: "))
intangible_assets = float(input("Intangible Assets: "))
# Liabilities
total_liabilities = float(input("Total Liabilities: "))
current_liabilities = float(input("Current Liabilities: "))
# total non current liabilities
long_term_debt = float(input("Long term debt: "))
stockholders_equity = float(input("Stockholders' Equity: "))
total_debt = float(input("Total Debt: "))
break
except ValueError:
print("please enter only numbers without comma and use . for decimals (e.g. 5.55 instead of 5,55")
continue
# Check asset increase
def check_assets_liabilities():
global assets_increase, liabilities_increase, receivable_increase
# Check assets
while True:
assets_increase_check = input("Have the total assets increased year over year? (y/n): ")
if not assets_increase_check.isalpha():
print("Please only enter y or n")
continue
if assets_increase_check == "y":
assets_increase = "y"
break
elif assets_increase_check == "n":
assets_increase = "n"
break
else:
print("Please only enter y or n")
continue
# Check liabilities
while True:
liabilities_increase_check = input("Have the total liabilities decreased year over year? (y/n): ")
if not liabilities_increase_check.isalpha():
print("Please only enter y or n")
continue
if liabilities_increase_check == "y":
liabilities_increase = "y"
break
elif liabilities_increase_check == "n":
liabilities_increase = "n"
break
else:
print("Please only enter y or n")
continue
return assets_increase, liabilities_increase
check_assets_liabilities()
balance_red_flags = 0
### Start writing to the report ###
file.write("\n\n\n_____________\n\nBalance Sheet Analysis\n_____________\n")
# Liquidatable assets
file.write("\n\n--- Assets that can be liquidated within 1 year ---\n")
file.write("\nTotal assets: %s\nCurrent assets: %s" % (
locale.currency(total_assets, grouping=True), locale.currency(current_assets, grouping=True)))
file.write("\nCash and cash equivalents: %s\nInventory value: %s" % (
locale.currency(cash, grouping=True), locale.currency(inventory, grouping=True)))
# Non-liquidatable assets
file.write("\n\n--- Non-current assets which can't be liquidated within 1 year ---\n")
file.write(
"\nNet PPE (property, plant and equipment): %s\n --> These are long term assets important for business operations.\nDepreciation: %s" % (
locale.currency(net_ppe, grouping=True), locale.currency(depreciation, grouping=True)))
file.write("\nIntangible Assets: %s --> Brand recognition, brand names, etc. How well the company is known." % locale.currency(intangible_assets, grouping=True))
# Liabilities
file.write("\n\n--- Liabilities ---\n")
file.write(
"\nTotal Liabilities: %s\nCurrent Liabilities: %s\nLong term debt: %s\n[!] Stockholders Equity: %s\nTotal debt: %s" % (
locale.currency(total_liabilities, grouping=True), locale.currency(current_liabilities, grouping=True),
locale.currency(long_term_debt, grouping=True), locale.currency(stockholders_equity, grouping=True),
locale.currency(total_debt, grouping=True)))
def analyze_balance():
"""Analyze the Balance sheet and write the results to the report"""
global balance_red_flags, assets_increase, liabilities_increase, stockholders_equity
file.write("\n\n\n[-->] Analyzing balance...\n")
if assets_increase == "n":
balance_red_flags += 1
file.write("\n[!] Assets are _not_ increasing.")
if liabilities_increase == "y":
balance_red_flags += 1
file.write("\n[!] Liabilities are increasing -> more debt is being accumulated.")
if stockholders_equity < 0:
balance_red_flags += 1
file.write("\n[!] Stockholders equity is negative. Liabilities are growing faster than assets.")
file.write("\n[!] Balance red flags: %s" % balance_red_flags)
return balance_red_flags
analyze_balance()
print("\n__________")
print("Cash Flow Analysis")
print("__________\n")
# Check balance numbers
while True:
try:
operating_cash_flow = float(input("Operating Cash Flow: "))
investing_cash_flow = float(input("Investing Cash Flow: "))
financing_cash_flow = float(input("Financing Cash Flow: "))
stock_compensation = float(input("Stock based compensation: "))
break
except ValueError:
print("please enter only numbers without comma and use . for decimals (e.g. 5.55 instead of 5,55")
continue
# Check income increase
def check_income():
global income_increase
while True:
income_increase_check = input("Is the company's net income increasing year over year? (y/n): ")
if not income_increase_check.isalpha():
print("Please only enter y or n")
continue
if income_increase_check == "y":
income_increase = "y"
break
elif income_increase_check == "n":
income_increase = "n"
break
else:
print("Please only enter y or n")
continue
return income_increase
check_income()
cash_red_flags = 0
### Start writing the report ###
file.write("\n\n\n_____________\n\nCash Flow Analysis\n_____________\n")
file.write(
"\nOperating cash flow: %s\nInvesting cash flow: %s\nFinancing cash flow: %s\nStock based compensation: %s" % (
locale.currency(operating_cash_flow, grouping=True), locale.currency(investing_cash_flow, grouping=True),
locale.currency(financing_cash_flow, grouping=True), locale.currency(stock_compensation, grouping=True)))
def analyze_cash():
global cash_red_flags, current_assets, current_liabilities, operating_cash_flow, investing_cash_flow, financing_cash_flow, income_increase, operating_cash_flow
file.write("\n\n\n[-->] Analyzing Cash-flow...\n")
working_capital = current_assets - current_liabilities
net_change_cash = operating_cash_flow - investing_cash_flow - financing_cash_flow
if income_increase == "n":
cash_red_flags += 1
file.write("\n[!] Income is not increasing each year -> take a look at the company's files to figure out why.")
if working_capital < 0:
cash_red_flags += 1
file.write(
"\n[!] Working capital negative: %s\n\t--> Company took on more debt or sold something to generate more money" % locale.currency(
working_capital, grouping=True))
if net_change_cash < 0:
cash_red_flags += 1
file.write(
"\n[!] Negative Net cash: %s\n\t--> Find out why and if it was warranted" % locale.currency(net_change_cash,
grouping=True))
if operating_cash_flow < 0:
cash_red_flags += 1
file.write(
"\nCash flow from financing activities is negative: %s\n\t--> Why? Where's the company's money coming from if they're not producing income?" % locale.currency(
operating_cash_flow, grouping=True))
return cash_red_flags, working_capital, net_change_cash
analyze_cash()
print("\n__________")
print("Intrinsic value analysis")
print("__________\n")
def check_age_forecast_commodity():
global age, forecast, commodity_reliance
# Check age
while True:
age_check = input("Is the company older than 10 years? (y/n): ")
if not age_check.isalpha():
print("Please only enter y or n")
continue
if age_check == "y":
age = "y"
break
elif age_check == "n":
age = "n"
break
else:
print("Please only enter y or n")
continue
# Check forecast
while True:
forecast_check = input("Do you still see it around in 10 years? (y/n): ")
if not forecast_check.isalpha():
print("Please only enter y or n")
continue
if forecast_check == "y":
forecast = "y"
break
elif forecast_check == "n":
forecast = "n"
break
else:
print("Please only enter y or n")
continue
# Check commodity reliance
while True:
commodity_check = input("Is the company distinguishable from others/ Does it have an economic moat? (y/n): ")
if not commodity_check.isalpha():
print("Please only enter y or n")
continue
if commodity_check == "y":
commodity_reliance = "y"
break
elif commodity_check == "n":
commodity_reliance = "n"
break
else:
print("Please only enter y or n")
continue
return age, forecast, commodity_reliance
check_age_forecast_commodity()
intrinsic_score = 0
def intrinsic_value():
global total_assets, total_liabilities, total_debt, current_price, net_income, stockholders_equity, income_red_flags, balance_red_flags, cash_red_flags, ebit, current_price, intrinsic_score
file.write(
"\n\n\n________________________________________\n\nIntrinsic value analysis\n\n________________________________________")
book_value = total_assets - total_liabilities
pbv = current_price / book_value
roe = '{0:.2f}%'.format((net_income / stockholders_equity * 100))
debt_to_equity_ratio = total_liabilities / stockholders_equity
rcoe = ebit / (total_assets - current_liabilities)
rcoe_to_price = rcoe * current_price
total_red_flags = income_red_flags + balance_red_flags + cash_red_flags
# Write to the report
file.write(
"\n\n\nBook value: %s --> Should be > 1. If the business went out of business now, how many times could it pay off all its debt.\nPrice to book value (P/BV): %s\n\t--> Should be > 1.5." % (locale.currency(book_value, grouping=True), pbv))
file.write(
"\nDebt to equity ratio: %s\n\t--> How much the company is financing its operations through debt." % debt_to_equity_ratio)
file.write(
"\nReturn on Equity (ROE): {}\n\t--> Should be > 10%. How effectively the management is using a company's assets to create profits.".format(
roe))
file.write(
"\nReturn on capital employed (RCOE): %s\n\ŧ--> Amount of profit a company is generating per 1$ employed -> good for peer comparison." % rcoe)
file.write(
"\nRCOE in comparison to price per share: %s\n\t--> Amount of money the company is generating per one share at the current price." % locale.currency(
rcoe_to_price, grouping=True))
# Calculate Score
if age == "y" or "Y": intrinsic_score += 1
if forecast == "y" or "Y": intrinsic_score += 1
if commodity_reliance == "y" or "Y": intrinsic_score += 1
if book_value > 0: intrinsic_score += 1
if pbv < 1.5: intrinsic_score += 1
if roe > 0.1: intrinsic_score += 1
if debt_to_equity_ratio < 1: intrinsic_score += 1
if total_red_flags < 1: intrinsic_score += 1
file.write(
"\n\n_________________________________\nFINAL INTRINSIC VALUE\n_________________________________\n\nIntrinsic value score: %s/8" % intrinsic_score)
if intrinsic_score <= 3:
file.write("\n\n[-->] Analysis: High risk!\n\t\t[>] Be careful investing into this companyand make sure to check the financial statements and company story again properly. Further research recommended!")
elif intrinsic_score == 4 or 5 or 6:
file.write("\n\n[-->] Analysis: Medium risk.\n\t\t[>] This company could be turning a profit but for safety reasons, please check the financial statements, red flags and other facts again, to be sure that nothing is inadvertently overlooked")
elif intrinsic_score == 7 or 8:
file.write("\n\n[-->] Analysis: Low risk.\n\t\t[>] It's unlikely that the company will go bankrupt in the foreseeable future.")
return book_value, pbv, roe, debt_to_equity_ratio, rcoe, total_red_flags, intrinsic_score
intrinsic_value()
print("\n\nDone.\n")
print("The intrinsic value score is: " + str(intrinsic_score) + "/8\n")
print(
"A report has been generated. Please check the same directory this program is located in\nThank you for using the Stock analysis tool.")
file.close()
Thank you for taking the time to read until here :)
1 Answer 1
Don't repeat yourself
Whenever you see that you are doing the same thing twice or more often in your program, find some way to avoid repeating yourself. For example, you have many instances of a while
-loop that just checks if someone entered a y
or n
. You can make a function for that:
def ask_yes_no(prompt):
while True:
answer = input(prompt + " (y/n): ")
if answer == "y":
return True
elif answer == "n":
return False
print("Please only enter y or n.")
And then you can use it like so:
def check_partner_trends():
...
if ask_yes_no("Does it have big partners?"):
partners = input("Who? ")
if ask_yes_no("Is it participating in any current trends?"):
trends = input("Which? ")
...
Avoid global variables
It is OK to have global variables if there is no better way to put them, but a major problem in your code is that your functions read from and write to those global variables, instead of getting the variables passed as function arguments and just returning them as return values. This prevents your functions from being reusable.
For example, in check_partner_trends()
, don't use global
variables partners
and trends
, only use local ones. You already return those, which is good. The caller can then decide in which variables to put those results. For example, it can just do:
partners, trends = check_partner_trends()
In the function analyze_income()
, pass the variables as parameters:
def analyze_income(gross_profit, total_revenue, operating_expenses, cost_of_revenue):
gross_margin = gross_profit / total_revenue
...
return gross_margin, operating_income, income_red_flags
gross_margin, operating_income, income_red_flags = analyze_income(gross_profit, total_revenue, operating_expenses, cost_of_revenue)
Separate logic from input/output
Many of your functions do not only implement the logic and calculations, but also read input and write to files. Try to separate these things, it will make the code more readable, and makes it much easier to reuse functions. For example, intrinsic_value()
not only calculates the intrinsic score, but it also writes it to file
. Create one function to calculate the value, and another to write out the results. In this case, you probably should avoid writing out anything until you have read all the input and processed it, and then have a single create_report()
function which itself opens the output file and prints the results to it.
-
\$\begingroup\$ Thank you very much, especially all the while loops gave me a headache, but I just couldn't get my head around how else to do it. I like the idea of the create_report() function, but how could I implement it, so that it writes everything in the right order, without being partly redundant itself, as it would mirror parts of functions like analyze_income (i.e. the red flag count)? @Peilonrayz I left the parts he was commenting in the old way, so as to prevent confusion \$\endgroup\$Seneo– Seneo2020年10月04日 22:39:43 +00:00Commented Oct 4, 2020 at 22:39
-
\$\begingroup\$ You could have
analyze_income()
build a list of strings, where each string is a description of the red flag. Then you ensurecreate_report()
gets this list, and then it can uselen()
on the list to get the number of red flags, and use afor
-loop to print a list of all the red flags encountered. \$\endgroup\$G. Sliepen– G. Sliepen2020年10月05日 07:18:23 +00:00Commented Oct 5, 2020 at 7:18 -
\$\begingroup\$ So something like this? There are some redundancies in the analyzing parts again, but I couldn't get it printing the numbers with a list + for loop as you suggested (e.g. ` file.write("\n[!] Balance red flags: %s" % balance_red_flags)`) \$\endgroup\$Seneo– Seneo2020年10月05日 10:59:52 +00:00Commented Oct 5, 2020 at 10:59