This post is part of a series of posts that we will go from complete scratch and build a portfolio management tool using Python.
In the last post, we defined project choices and started to code, adding a database handler and a simple UI to work with.
Here is the previous post in case you missed it:
Plan for this post
For this post, we will refactor the file organization and also build a function for adding stocks to our “database” (JSON)
Starting to code..
First, we did a small tweak in the config.py file. Instead of defining DATABASE_NAME directly, we defined a DATA_FOLDER thing, that will help with future data we must store so that everything will go under this folder data.
$ config.py
# database JSON file name
DATA_FOLDER = 'data/'
DATABASE_NAME = DATA_FOLDER + 'database.json'
Another thing that was ugly in the last post was the main file. We had a UI menu there, and we wanted to have a separate file to handle UI interaction. That’s why we created the ui.py file, under a new folder view. I will show the code there shortly.
Now to the new code. Let’s create a function under view/ui.py new file that is called add_stock().
This function is responsible for receiving inputs about the stock the user wants to input. For now, let’s ask for stock_ticker, quantity, price_per_share (the user bought it), and purchase_date. We can modify it later
$ view/ui.py
...
def add_stock():
# Prompt user for stock details
stock_ticker = input("Enter the stock ticker: ")
# Prompt user for quantity with error checking
while True:
try:
quantity = float(input("Enter the quantity bought: "))
if quantity < 0:
raise ValueError("Quantity must be a non-negative number.")
break
except ValueError as ve:
print(f"Error: {ve}")
# Prompt user for price per share with error checking
while True:
try:
price_per_share = float(input("Enter the price per share in dollars: "))
if price_per_share < 0:
raise ValueError("Price per share must be a non-negative number.")
break
except ValueError as ve:
print(f"Error: {ve}")
# Prompt user for purchase date with error checking
while True:
try:
purchase_date_str = input("Enter the purchase date (YYYY-MM-DD): ")
purchase_date = datetime.strptime(purchase_date_str, '%Y-%m-%d').date()
break
except ValueError:
print("Error: Please enter a valid date in the format YYYY-MM-DD.")
# Add stock details to JSON file
return stock_ticker, quantity, price_per_share, purchase_date_str
...
Note that we added some controls over the data being added since we don’t want negative quantities/prices per share or invalid dates. About the tickers, we are not doing validation right now, but we will change this later.
This function reads the data and sends it over to somewhere else… Well, this “somewhere else” will be implemented in a function called add_stock under database_handler.py file, since we’ve been controlling data handling there and we want this stock info to go into our JSON.
Note that we also changed the database_handler file a bit. Specifically, we added a function for setting up the “data” folder and for getting the database, which will become handy soon:
$ database_handler.py
import json
import os
import config
# this class handle the database files
class databaseHandler:
def __init__(self, database_name = config.DATABASE_NAME):
self.database_name = database_name
self.setup_folders()
self.load_database()
# setup folders needed by the application
def setup_folders(self):
if not os.path.exists(config.DATA_FOLDER):
os.makedirs(config.DATA_FOLDER)
# load database content
def load_database(self):
print("Loading database content..")
try:
with open(self.database_name, 'r') as file:
print("Found database, loading it")
self.database_data = json.load(file)
except FileNotFoundError:
print(f"Database '{self.database_name}' not found. Creating a new one.")
with open(self.database_name, 'w') as new_file:
new_file.write('{}')
self.database_data = {}
def print_full_database(self):
print("\nPrint database content..")
try:
print(json.dumps(self.database_data, indent=2))
except Exception as e:
print(f"Error printing database: {e}")
def add_stock(self, stock_ticker, quantity, price_per_share, purchase_date):
stock_entry = {
'stock_ticker': stock_ticker,
'quantity': quantity,
'price_per_share': price_per_share,
'purchase_date': purchase_date
}
if 'portfolio' not in self.database_data:
self.database_data['portfolio'] = []
# Add new input to the portfolio
self.database_data['portfolio'].append(stock_entry)
# Saving in JSON file
with open(config.DATABASE_NAME, 'w') as file:
json.dump(self.database_data, file, indent=2)
print("Stock details added successfully.")
def get_database(self):
return self.database_data
By the code, you can notice we will have a key inside the JSON database that is called “portfolio“. This key is a list of stocks that we have in our portfolio. Now, for calling this “add_stock” function from ui/database_handler, we need a new method option in the menu.
Here is what the menu function will look like:
$ view/ui.py
...
def menu(database_handler):
while True:
print("\n--- Methods Menu ---")
print("1 - Print DATABASE file content")
print("2 - Add a stock to your portfolio")
print("0 - Exit")
option = input("Choose an option: ")
try:
option = int(option)
except ValueError:
print("Invalid option. Please enter a number.")
continue
if option == 1:
database_handler.print_full_database()
elif option == 2:
stock_ticker, quantity, price_per_share, purchase_date = add_stock()
database_handler.add_stock(stock_ticker, quantity, price_per_share, purchase_date)
elif option == 0:
break
else:
print("Invalid option. Please try again.")
Note that this function will call add_stock from the ui (they are in the same file), and then with the inputs from the user it will call database_handler.add_stock.
Another thing to notice is that this function expects a database_handler. This is because the main function will now call the menu passing the handler to it.
Here is what the main.py looks like now
$ main.py
from database_handler import databaseHandler
import view.ui as ui
if __name__ == "__main__":
database_handler = databaseHandler()
ui.menu(database_handler)
Ok, now let’s test it by running python main.py.
We will be testing to add to our portfolio the APPL ticker, bought 20 @ 120.0 on January 31, 2024

Now let’s check the JSON we have in database.json
$ database.json
{
"portfolio": [
{
"stock_ticker": "APPL",
"quantity": 20.0,
"price_per_share": 120.0,
"purchase_date": "2024-01-31"
}
]
}
This is looking nice!
Now, let’s add a function to tell us how much we pay for our portfolio. For that, we need to iterate over portfolio items and sum the quantity * price per share.
First, let’s create a new handler that will be useful later — this handler deals with operations in the portfolio. For now, this will only contain the sum_portfolio function:
$ operations_handler.py
class operationsHandler:
def __init__(self, database_handler):
self.database_handler = database_handler
def sum_portfolio(self):
database = self.database_handler.get_database()
total_value = 0.0
for stock_entry in database["portfolio"]:
quantity = stock_entry["quantity"]
price_per_share = stock_entry["price_per_share"]
total_value += quantity * price_per_share
return total_value
Now, in the ui file let’s create a menu for operations. In the future, we will have other operations with the portfolio. Here’s the full view/ui.py
$ view/ui.py
from datetime import datetime
from operations_handler import operationsHandler
def add_stock():
# Prompt user for stock details
stock_ticker = input("Enter the stock ticker: ")
# Prompt user for quantity with error checking
while True:
try:
quantity = float(input("Enter the quantity bought: "))
if quantity < 0:
raise ValueError("Quantity must be a non-negative number.")
break
except ValueError as ve:
print(f"Error: {ve}")
# Prompt user for price per share with error checking
while True:
try:
price_per_share = float(input("Enter the price per share in dollars: "))
if price_per_share < 0:
raise ValueError("Price per share must be a non-negative number.")
break
except ValueError as ve:
print(f"Error: {ve}")
# Prompt user for purchase date with error checking
while True:
try:
purchase_date_str = input("Enter the purchase date (YYYY-MM-DD): ")
purchase_date = datetime.strptime(purchase_date_str, '%Y-%m-%d').date()
break
except ValueError:
print("Error: Please enter a valid date in the format YYYY-MM-DD.")
# Add stock details to JSON file
return stock_ticker, quantity, price_per_share, purchase_date_str
def operations(database_handler):
operations_handler = operationsHandler(database_handler)
while True:
print("\n--- Operations Menu ---")
print("1 - Show total spent in the portfolio")
print("0 - Exit")
option = input("Choose an option: ")
try:
option = int(option)
except ValueError:
print("Invalid option. Please enter a number.")
continue
if option == 1:
portolio_sum = operations_handler.sum_portfolio()
print(f"Portfolio Sum: {portolio_sum}")
elif option == 0:
break
else:
print("Invalid option. Please try again.")
def menu(database_handler):
while True:
print("\n--- Methods Menu ---")
print("1 - Print DATABASE file content")
print("2 - Add a stock to your portfolio")
print("3 - Basic Operations")
print("0 - Exit")
option = input("Choose an option: ")
try:
option = int(option)
except ValueError:
print("Invalid option. Please enter a number.")
continue
if option == 1:
database_handler.print_full_database()
elif option == 2:
stock_ticker, quantity, price_per_share, purchase_date = add_stock()
database_handler.add_stock(stock_ticker, quantity, price_per_share, purchase_date)
elif option ==3:
operations(database_handler)
elif option == 0:
break
else:
print("Invalid option. Please try again.")
And that’s it. Notice we are adding option 3 in the Methods menu. This option leads to the Operations menu, in which we can “Show total spent in the portfolio”.
Before testing, let’s add to our portfolio the MSFT ticker, bought 10 @ 400.0 on January 30, 2024. Here is how the database.json looks like
$ database.json
{
"portfolio": [
{
"stock_ticker": "APPL",
"quantity": 20.0,
"price_per_share": 120.0,
"purchase_date": "2024-01-31"
},
{
"stock_ticker": "MSFT",
"quantity": 10.0,
"price_per_share": 400.0,
"purchase_date": "2024-01-30"
}
]
}
Now to the test

20*120 (= 2400) + 10*400 (= 4000) = 6400 dollars!
Yes, the code looks right. Now we can see how much we invested.
What if we want to delete an entry from the portfolio? Or maybe do total calculations for a specific ticker? Well.. this is what we plan to cover in the next post.
That was it for this post!
Hope you are enjoying this series and let me know in the comments what to cover next!