Portfolio Tracker 📉

Preview Image

Takes the user's portfolio as input and weighs the performance against stock market indicies or another custom portfolio.

This program takes the user’s stock portfolio as input and weighs its performance against stock market indicies or another custom portfolio.

First, install the ‘yfinance’ Python library. This is a Python library with tools to query stock market data from Yahoo Finance.

pip install yfinance

Now, the full source code for this program:

import yfinance as yf


# ANSI escape sequence for red color
RED = "\033[91m"
# ANSI escape sequence for green color
GREEN = "\033[92m"
# ANSI escape sequence for blue color
BLUE = "\033[94m"
# ANSI escape sequence for yellow color
YELLOW = "\033[93m"
# ANSI escape sequence for orange color
ORANGE = "\033[91m"
# ANSI escape sequence to reset color
RESET = "\033[0m"


print(f"\n{GREEN}Welcome to Portfolio Tracker!{RESET}")

#Begins the program
def program_start():
    user_choice = input(''''\n\nEnter a number to choose your action.
    1. Weigh your portfolio against indicies. 
    2. Weigh your portfolio against another custom portfolio.
    3. Exit program. \n'''')
    if user_choice == "1":
        user_vs_indicies()
    elif user_choice =="2":
        user_vs_custom_portfolio()
    elif user_choice == "3":
        return()

#This function calculates the percent change, taking 2 integers representing $USD amounts 
def percent_change(first_value, second_value):

    a = (((float(second_value)-first_value)/(first_value))*100.00)

    return(a)

#This function calculates the opening price of a stock at the beginning of the user-defined time period,
#using the very first position of the first column (the opening price column) of a price history table 
#generated by the yfinance.history() method
def beginning_open(price_history, shares):
    b = price_history.iloc[0,0] * shares

    return(b)

#This function calculates the most recent closing price of a stock. It looks at the same table that the 
#beginning_open() function looks at, but it takes the last position in the 4th column of the table 
#(the closing price column)

def recent_close(price_history, shares):
    c = price_history.iloc[-1,3] * shares

    return(c)

#This function calculates the final $USD total of the user's portfolio at the beginning and the end of the 
#user-defined period of time. These totals are then passed into the percent_change() function
def final_total(beginning_list, end_list):
    
    beginning_total = sum(beginning_list)
    end_total = sum(end_list)

    d = percent_change(beginning_total, end_total)
    return(d)

#Calculates the user's performance against the indicies and prints a snarky comment
def snarky_comment(your_portfolio, spy, qqq):
    if float(your_portfolio) < float(spy) and float(qqq):
        
        print(f"\n{RED}Maybe investing isn't for you, buddy.{RESET}")

    elif float(your_portfolio) > float(spy) and float(qqq):
        
        print(f"\n{GREEN}Wow look at Warren Buffet over here.{RESET}")

#This function is the main loop of the program. This takes a user-defined period of time, queries the user on a 
#stock ticker that they want to add to the portfolio, asks them how many shares, then adds the $USD value to 2 
#separate lists: a list of prices from the beginning of the time period, and a list of prices from the end
def portfolio_creation(user_period):

    print(f"\n{BLUE}New Portfolio{RESET}")

    beginning_list = []

    end_list = []

    user_tickers = []


    a = 1

    while a > 0:
        print('''\nType 'done' to finish building portfolio. 
            Type 'undo' to delete the most recent addition to your portfolio.''')
        stock_input = input("Enter a stock ticker: ")

        if stock_input == "done":
            if len(beginning_list) and len(end_list) == 0:
                print(f"\n{RED}Please enter a at least 1 ticker.{RESET}")
            elif sum(beginning_list) == 0 or sum(end_list) == 0:
                beginning_list.clear()
                end_list.clear()
                print(f"\n{RED}Please enter a share count greater than 0.{RESET}")
            else:
                a = 0
        elif stock_input == "undo":
            if len(beginning_list) == 0 or len(end_list) == 0:
                print(f"{RED}Nothing to undo.{RESET}")
            else:
                beginning_list.pop()
                end_list.pop()
                user_tickers.pop()    
                print(f"\n{BLUE}Current stocks in portfolio: {RESET}")
                for stock in user_tickers:
                    print(f"{YELLOW}${stock}{RESET}")
        else:
            try:
                stock = yf.Ticker(stock_input.upper())
                stock_info = stock.info

                # Fetches the opening price of a stock from a user-defined period of time
                stock_history = stock.history(period=user_period)
                shares = float(input("Enter number of shares: "))

                stock_beginning_open = beginning_open(stock_history, shares)
                stock_recent_close = recent_close(stock_history, shares)

                # This part of the function appends the beginning and ending prices to their 
                # respective lists located at the beginning of the function.
                beginning_list.append(stock_beginning_open)
                end_list.append(stock_recent_close)

                #Displays the current list of stocks in user's portfolio
                user_tickers.append(stock_input.upper())
                print(f"\n{BLUE}Current stocks in portfolio: {RESET}")
                for stock in user_tickers:
                    print(f"{YELLOW}${stock}{RESET}")

            except Exception as e:
                print(f"\n{RED}Invalid ticker symbol. Please enter a valid ticker symbol.{RESET}")
                
    
    total = final_total(beginning_list, end_list)

    print(f"\n{BLUE}Portfolio finished.\n\n-----------------------------------------------------------------{RESET}")

    return(total)

def user_vs_indicies():
    user_period = input("\nEnter a time period. Valid periods are: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max.\n")
    user = portfolio_creation(user_period)
    spy = yf.Ticker('SPY')
    qqq = yf.Ticker('QQQ')

    spy_ytd_history = spy.history(period = user_period)
    qqq_ytd_history = qqq.history(period = user_period)

    spy_start = spy_ytd_history.iloc[0, 0]
    spy_end = spy_ytd_history.iloc[-1, 3]

    qqq_start = qqq_ytd_history.iloc[0, 0]
    qqq_end = qqq_ytd_history.iloc[-1, 3]


    spy_ytd_performance = percent_change(spy_start, spy_end)
    qqq_ytd_performance = percent_change(qqq_start, qqq_end)


    print("\n\nYour portfolio " + user_period.upper() + " performance: " + str(user) + "%")

    print("SPY " + user_period.upper() + " performance: " + str(spy_ytd_performance) + "%")

    print("QQQ " + user_period.upper() + " performance: " + str(qqq_ytd_performance) + "%")

    snarky_comment(user, spy_ytd_performance, qqq_ytd_performance)


    user_choice = input('''\n\nEnter a number to choose your action.
    1. Create a new portfolio.
    2. Return to main menu.
    3. Exit program. \n''')

    if user_choice == "1":
        user_vs_indicies()
    elif user_choice =="2":
        program_start()
    elif user_choice == "3":
        return()

def user_vs_custom_portfolio():

    user_period = input("\nEnter a time period. Valid periods are: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max.\n")
    portfolio1 = portfolio_creation(user_period)
    portfolio2 = portfolio_creation(user_period)

    print("\n\nPortfolio 1 " + user_period.upper() + " performance: " + str(portfolio1) + "%")
    print("\n\nPortfolio 2 " + user_period.upper() + " performance: " + str(portfolio2) + "%")
    
    user_choice = input('''\n\nEnter a number to choose your action.
    1. Create new portfolios.
    2. Return to main menu.
    3. Exit program. \n''')

    if user_choice == "1":
        user_vs_custom_portfolio()
    elif user_choice =="2":
        program_start()
    elif user_choice == "3":
        return()  

program_start()