In this blog post, we will walk through the process of building a simple yet powerful Stock Portfolio Tracker using Python, Tkinter for the GUI, SQLite for storing stock data, and Yahoo Finance to fetch real-time stock prices.
Why Build a Stock Portfolio Tracker?
Managing a stock portfolio involves tracking multiple stocks, their current prices, the quantity owned, and calculating the gains or losses over time. While there are many commercial tools to handle stock tracking, building your own gives you full control over customization and functionality.
With our Stock Portfolio Tracker, we can:
- Add stocks to the portfolio with details like stock name, quantity, and purchase price.
- Track live stock prices using the Yahoo Finance API.
- Automatically calculate the gain/loss based on the difference between the purchase price and the current stock price.
- Store all stock details persistently using an SQLite database.
Let’s dive into how we can build this application step-by-step!
Key Components of the Application
- Tkinter for GUI: We will use Tkinter, Python's standard GUI library, to create a simple interface where users can add, view, and delete stock information.
- SQLite for Database: SQLite will be used to store stock details persistently. This ensures that data is not lost between application runs.
- Yahoo Finance API: We’ll use the
yfinance
library to fetch real-time stock prices, allowing us to calculate the gain/loss for each stock.
Prerequisites
Before we start, make sure you have Python installed on your machine. Additionally, you need to install the following libraries:
- Tkinter (comes pre-installed with Python for GUI).
- yfinance for querying stock prices.
Install yfinance
using pip:
pip install yfinance
The Application Walkthrough
1. Setting Up SQLite Database
The application stores stock data in an SQLite database. We create a table with columns for stock name, quantity, purchase price, and gain/loss.
def create_db():
conn = sqlite3.connect('stock_portfolio.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS stocks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
quantity INTEGER,
purchase_price REAL,
gain_loss REAL DEFAULT 0
)
''')
conn.commit()
conn.close()
2. Adding Stock to the Database
When adding a stock, the user inputs the stock name (symbol), quantity, and purchase price. If the stock symbol is valid (checked via Yahoo Finance), the data is stored in the SQLite database.
def add_stock_to_db(name, quantity, purchase_price):
conn = sqlite3.connect('stock_portfolio.db')
c = conn.cursor()
c.execute('''
INSERT INTO stocks (name, quantity, purchase_price)
VALUES (?, ?, ?)
''', (name, quantity, purchase_price))
conn.commit()
conn.close()
3. Retrieving Stock Prices from Yahoo Finance
To calculate the gain/loss for each stock, we need to fetch the live stock price using the yfinance
library. The get_current_price
function retrieves the latest closing price of a stock.
def get_current_price(stock_name):
try:
stock = yf.Ticker(stock_name)
data = stock.history(period='1d')
return data['Close'].iloc[-1]
except Exception as e:
print(f"Error fetching price for {stock_name}: {e}")
return None
4. Calculating Gain/Loss
The gain or loss is calculated as the difference between the current stock price and the purchase price, multiplied by the quantity of stocks owned.
def update_stock_info():
stocks = get_all_stocks()
for stock in stocks:
stock_id, name, quantity, purchase_price, gain_loss = stock
current_price = get_current_price(name)
if current_price is not None:
gain_loss = (current_price - purchase_price) * quantity
update_stock_in_db(stock_id, gain_loss)
5. GUI for Stock Portfolio
The Tkinter interface allows the user to:
- Add a stock: By inputting the stock symbol, quantity, and purchase price.
- View stocks: The stocks are displayed in a list box, showing the stock name, quantity, purchase price, current price, and gain/loss.
- Delete a stock: Users can select a stock and delete it from the portfolio.
- Refresh the portfolio: Users can manually refresh the gain/loss or wait for the automatic refresh every 60 seconds.
def update_stock_listbox():
stock_listbox.delete(0, tk.END)
stocks = get_all_stocks()
for stock in stocks:
stock_id, name, quantity, purchase_price, gain_loss = stock
current_price = get_current_price(name)
if current_price is not None:
gain_loss = (current_price - purchase_price) * quantity
stock_info = f"{name} | Quantity: {quantity} | Price: {current_price:.2f} | Gain/Loss: {gain_loss:.2f}"
stock_listbox.insert(tk.END, (stock_id, stock_info))
Handling Invalid Stock Symbols
When a user enters an invalid stock symbol, the application checks the symbol using the yfinance
library. If the symbol is invalid (i.e., Yahoo Finance cannot retrieve data), an error message is shown.
def is_valid_symbol(stock_name):
try:
stock = yf.Ticker(stock_name)
data = stock.history(period='1d')
if data.empty:
return False
return True
except Exception:
return False
6. Refreshing the Stock Data
The stock prices are refreshed every 60 seconds using the root.after()
function, ensuring that the stock portfolio stays up-to-date with the latest market prices.
root.after(60000, update_stock_info) # Update every 60 seconds (1 minute)
Complete Code
Here’s the complete code for the Stock Portfolio Tracker:
# Insert the full code from the previous section here
Key Features
- Add, View, and Delete Stocks: A simple Tkinter-based interface lets users manage their stock portfolio.
- Real-Time Price Updates: Stocks are tracked with live prices fetched from Yahoo Finance.
- Automatic Gain/Loss Calculation: Gain or loss is calculated based on the current price and the original purchase price.
- Persistent Data: Stocks are stored in an SQLite database, so the portfolio persists even after closing the application.
- Error Handling: Invalid stock symbols are handled with clear error messages.
Conclusion
This Stock Portfolio Tracker provides a simple and effective way to manage your stock investments. It combines the power of Python, Tkinter, SQLite, and Yahoo Finance to offer a real-time stock tracking solution with a clean and intuitive interface.
You can easily expand this project by adding features such as:
- Portfolio summary (total value, total gain/loss).
- Historical price data visualization (e.g., using
matplotlib
orPlotly
). - Alerts for price changes or gains/losses exceeding a threshold.
By building this application, you’ve learned how to integrate a GUI, database, and external APIs in Python to solve real-world problems. Happy coding!
Complete code
"""
Python Application - Stock Market Portfolio Tracker App
"""
import tkinter as tk
from tkinter import messagebox
import sqlite3
import yfinance as yf
def create_db():
conn = sqlite3.connect('stock.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS stocks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
quantity INTEGER,
price REAL,
value REAL DEFAULT 0
)
''')
conn.commit()
conn.close()
def insert_stock_db(name, quantity, price):
conn = sqlite3.connect('stock.db')
c = conn.cursor()
c.execute('''
INSERT INTO stocks (name, quantity, price)
VALUES (?, ?, ?)
''', (name, quantity, price))
conn.commit()
conn.close()
def read_all_stocks_db():
conn = sqlite3.connect('stock.db')
c = conn.cursor()
c.execute('SELECT id, name, quantity, price, value FROM stocks')
stocks = c.fetchall()
conn.close()
return stocks
def update_stock_value_db(stock_id, value):
conn = sqlite3.connect('stock.db')
c = conn.cursor()
c.execute('''
UPDATE stocks SET value = ?
WHERE id = ?
''', (value, stock_id))
conn.commit()
conn.close()
def delete_stock_db():
try:
selected = stock_listbox.curselection()
if selected:
stock_id = stock_listbox.get(selected[0])[0] # Get the stock ID from the selected item
conn = sqlite3.connect('stock.db')
c = conn.cursor()
c.execute('DELETE FROM stocks WHERE id = ?', (stock_id,))
conn.commit()
conn.close()
update_stock_listbox()
else:
messagebox.showwarning("No Selection", "Please select a stock to delete.")
except Exception as e:
messagebox.showerror("Error", f"An error occurred while deleting the stock: {e}")
def get_current_price(stock_name):
try:
stock = yf.Ticker(stock_name)
data = stock.history(period='1d')
return data['Close'].iloc[-1]
except Exception as e:
print(f"Error fetching price for {stock_name}: {e}")
return None
def update_stock_listbox():
stock_listbox.delete(0, tk.END) # delete existing content
stocks = read_all_stocks_db()
for stock in stocks:
stock_id, name, quantity, price, value = stock
current_price = get_current_price(name)
if current_price is not None:
value = (current_price - price) * quantity
stock_info = f"{name} | Quantity: {quantity} | Price: {current_price:.2f} | Gain/Loss: {value:.2f}"
stock_listbox.insert(tk.END, (stock_id, stock_info)) # insert new content
update_stock_value_db(stock_id, value)
def add_stock():
try:
name = input_stock_name.get()
quantity = int(input_stock_quantity.get())
price = float(input_stock_price.get())
if yf.Ticker(name).history(period='1d').empty:
messagebox.showerror("Invalid Stock Symbol", f"The stock symbol '{name}' is not valid.")
return
insert_stock_db(name, quantity, price)
update_stock_listbox()
except Exception as e:
messagebox.showerror("Invalid Input", f"Please enter valid quantity and price values. {e}")
finally:
input_stock_name.delete(0, tk.END)
input_stock_quantity.delete(0, tk.END)
input_stock_price.delete(0, tk.END)
root = tk.Tk()
root.title("Stock Portfolio Tracker")
input_frame = tk.Frame(root)
input_frame.pack(padx=10, pady=10)
tk.Label(input_frame, text="Stock Name:").grid(row=0, column=0)
input_stock_name = tk.Entry(input_frame)
input_stock_name.grid(row=0, column=1)
tk.Label(input_frame, text="Quantity:").grid(row=1, column=0)
input_stock_quantity = tk.Entry(input_frame)
input_stock_quantity.grid(row=1, column=1)
tk.Label(input_frame, text="Buy Price:").grid(row=2, column=0)
input_stock_price = tk.Entry(input_frame)
input_stock_price.grid(row=2, column=1)
add_button = tk.Button(input_frame, text="Add Stock", command=add_stock)
add_button.grid(row=3, columnspan=2, pady=10)
stock_frame = tk.Frame(root)
stock_frame.pack(padx=10, pady=10)
stock_listbox = tk.Listbox(stock_frame, width=60, height=10)
stock_listbox.pack()
delete_button = tk.Button(root, text="Delete Stock", command=delete_stock_db)
delete_button.pack(side=tk.LEFT, padx=10, pady=10)
refresh_button = tk.Button(root, text="Refresh Stock", command=update_stock_listbox)
refresh_button.pack(side=tk.LEFT, padx=10, pady=10)
create_db()
update_stock_listbox()
root.after(60000, update_stock_listbox)
root.mainloop()
0 Comments