3
\$\begingroup\$

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 :)

\$\endgroup\$
0

1 Answer 1

5
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\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
    Commented 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 ensure create_report() gets this list, and then it can use len() on the list to get the number of red flags, and use a for-loop to print a list of all the red flags encountered. \$\endgroup\$
    – G. Sliepen
    Commented 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
    Commented Oct 5, 2020 at 10:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.