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 refactored the file organization and built a function for adding stocks to the “database”.
Here are the previous posts in case you missed them:
- 1 – Building a Portfolio Manager with Python: First steps
- 2 – Building a Portfolio Manager with Python: Adding stocks
Plan for this post
For this post, we will refactor our database to treat it as orders instead of stocks. For that, we will add an ID, also an operation (‘B’ for buy and ‘S’ for sell), and also add a function to remove old order stocks from our “database” (JSON)
Starting to code…
First, we did some updates to the database_handler.py file. We updated the init function to store the next_order_id.
$ database_handler.py
class databaseHandler:
def __init__(self, database_name = config.DATABASE_NAME):
self.database_name = database_name
self.setup_folders()
self.load_database()
self.next_order_id = self.get_last_order_id() + 1
Some other things we need to code are the get_order, remove_stock_order, and get_last_order_id functions. Also, we need to update the add_stock function to become the add_stock_order function to start asking for the operation (buy or sell)
$ database_handler.py
...
# get order by order ID
def get_order(self, id):
if 'orders' not in self.database_data:
return
for order in self.database_data['orders'] :
if order.get('id') == id:
return order
return None
def add_stock_order(self, op, stock_ticker, quantity, price_per_share, purchase_date):
stock_entry = {
'id': self.next_order_id,
'op': op,
'stock_ticker': stock_ticker,
'quantity': quantity,
'price_per_share': price_per_share,
'purchase_date': purchase_date
}
if 'orders' not in self.database_data:
self.database_data['orders'] = []
# Add new input to the orders
self.database_data['orders'].append(stock_entry)
# Saving in JSON file
with open(config.DATABASE_NAME, 'w') as file:
json.dump(self.database_data, file, indent=2)
self.next_order_id += 1
print("Order details added successfully.")
def remove_stock_order(self, id):
if 'orders' not in self.database_data:
return
# Fetch all orders except the deleted id
self.database_data['orders'] = [order for order in self.database_data['orders'] if order.get('id') != id]
# Saving in JSON file
with open(config.DATABASE_NAME, 'w') as file:
json.dump(self.database_data, file, indent=2)
self.next_order_id += 1
print(f'Order {id} removed successfully.')
def get_database(self):
return self.database_data
def get_last_order_id(self):
last_id = -1
if 'orders' not in self.database_data:
# first order added, we return -1
return last_id
for item in self.database_data['orders']:
if item['id'] > last_id:
last_id = item['id']
return last_id
Note that we updated the name from portfolio to orders in the JSON database. Because of that, we need to make a small change to the operations_handler. Also, on the sum_portfolio operation, we will separate the Buy orders from the Sell orders, so let’s do a tweak in the function:
$ operations_handler.py
...
# sums portfolio by operation type
def sum_portfolio(self, order_type):
database = self.database_handler.get_database()
total_value = 0.0
for stock_entry in database["orders"]:
if 'op' in stock_entry and stock_entry['op'] == order_type:
quantity = stock_entry["quantity"]
price_per_share = stock_entry["price_per_share"]
total_value += quantity * price_per_share
return total_value
Now, from view/ui.py, we can update the menu, the operations menu, and the add_stock functions to accommodate our changes. Finally, we will also add the remove_stock_order option
$ view/ui.py
...
# adds stock order in database
def add_stock_order():
# Prompt user for operation
while True:
op = input("Enter the order operation -- Buy or Sell (B or S): ")
if op == "B" or op == "S":
break
else:
print("Error: Please enter a valid operation (B or S)")
# 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 op, stock_ticker, quantity, price_per_share, purchase_date_str
def remove_stock_order(database_handler):
# Prompt user for operation
while True:
try:
id = int(input("Enter the order id you want to delete: (-1 to exit): "))
if (id == -1):
break
order = database_handler.get_order(id)
if order != None:
confirm = input(f'Are you sure you want to delete order {id} ? (y or n): ')
if (confirm == 'y'):
break
else:
print("Error: Please enter a valid order id")
except:
print("Invalid id number, please try again")
return id
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:
buy_operations_sum = operations_handler.sum_portfolio(order_type = 'B')
sell_operations_sum = operations_handler.sum_portfolio(order_type = 'S')
print(f"Buy operations sum: {buy_operations_sum}")
print(f"Sell operations sum: {sell_operations_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 an order to your portfolio")
print("3 - Remove an order from your portfolio")
print("4 - 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:
op, stock_ticker, quantity, price_per_share, purchase_date = add_stock_order()
database_handler.add_stock_order(op, stock_ticker, quantity, price_per_share, purchase_date)
elif option == 3:
database_handler.print_full_database()
id_to_remove = remove_stock_order(database_handler)
database_handler.remove_stock_order(id_to_remove)
elif option == 4:
operations(database_handler)
elif option == 0:
break
else:
print("Invalid option. Please try again.")
Ok, that is looking promising! Let’s use the flow to add a few stocks by running python main.py. Notice we added two fictional orders, buying 10 APPL @ 100 and selling 50 NVDA @ 20.

And now let’s check the new database.json file, after our changes:
{
"orders": [
{
"id": 0,
"op": "B",
"stock_ticker": "AAPL",
"quantity": 10.0,
"price_per_share": 100.0,
"purchase_date": "2024-12-21"
},
{
"id": 1,
"op": "S",
"stock_ticker": "NVDA",
"quantity": 50.0,
"price_per_share": 20.0,
"purchase_date": "2024-12-21"
}
]
}
That is looking good, let’s try to do the sum operation for the portfolio:

Ok, it is looking good. We have a thousand dollars for buying operations, and also a thousand dollars for selling operations, since 100*10 = 1,000 and 50*20 is also 1,000.
Now let’s try to remove the NVDA order, let’s assume it is wrong so we need to remove it:

Oh, now let’s check our database:
{
"orders": [
{
"id": 0,
"op": "B",
"stock_ticker": "AAPL",
"quantity": 10.0,
"price_per_share": 100.0,
"purchase_date": "2024-12-21"
}
]
}
That is great, the NVDA order is gone! The remove order function is working!
What if we want to do total calculations for a specific ticker, assuming we have more than one order? 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!