Automating Finance Tasks with Python
Build scripts that track your portfolio, parse expenses, generate invoices, and alert you when prices move.
The Power of Finance Automation
Most personal finance tasks are repetitive: checking portfolio values, categorising expenses, chasing invoices, watching stock prices. Python can do all of this automatically while you focus on actual decision-making. In this tutorial we build four practical scripts you can start using today.
pip install schedule yfinance fpdf2 pandas1. Daily Portfolio Emailer
This script fetches live stock prices using yfinance, calculates your portfolio value, and emails you a daily summary. Use a Gmail app password (not your real password) for authentication. It takes five minutes to set up and gives you a daily snapshot without opening any app.
import smtplib
import yfinance as yf
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
def get_portfolio_value():
"""Fetch current prices for your holdings."""
holdings = {
'AAPL': 10, # 10 shares of Apple
'MSFT': 5, # 5 shares of Microsoft
'GOOGL': 3, # 3 shares of Google
'AMZN': 8, # 8 shares of Amazon
}
total = 0
lines = []
for symbol, shares in holdings.items():
ticker = yf.Ticker(symbol)
price = ticker.info.get('currentPrice', 0)
value = price * shares
total += value
lines.append(f'{symbol}: {shares} shares x ${price:.2f} = ${value:.2f}')
return total, lines
def send_daily_email():
"""Email yourself the daily portfolio summary."""
total, lines = get_portfolio_value()
body = f'Portfolio Summary - {datetime.now().strftime("%d %b %Y")}\n'
body += f'=' * 40 + '\n'
body += '\n'.join(lines)
body += f'\n\nTotal Value: ${total:,.2f}'
msg = MIMEMultipart()
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
msg['Subject'] = f'Portfolio: ${total:,.2f} - {datetime.now().strftime("%d %b")}'
msg.attach(MIMEText(body, 'plain'))
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login('[email protected]', 'your-app-password')
server.send_message(msg)
print(f'Email sent! Portfolio value: ${total:,.2f}')2. Expense CSV Parser
Export your bank transactions as CSV and let Python categorise them automatically. The script uses keyword matching to sort transactions into categories like groceries, transport, and subscriptions. It produces a summary showing exactly where your money goes each month.
import pandas as pd
from datetime import datetime
def parse_expenses(csv_file):
"""Parse a bank CSV export and categorise expenses."""
df = pd.read_csv(csv_file)
# Define category rules
categories = {
'Groceries': ['tesco', 'sainsbury', 'aldi', 'lidl', 'waitrose'],
'Transport': ['uber', 'tfl', 'trainline', 'shell', 'bp'],
'Subscriptions': ['netflix', 'spotify', 'amazon prime', 'gym'],
'Eating Out': ['deliveroo', 'just eat', 'nando', 'mcdonald'],
'Bills': ['british gas', 'thames water', 'bt', 'o2'],
}
def categorise(description):
desc_lower = description.lower()
for category, keywords in categories.items():
if any(kw in desc_lower for kw in keywords):
return category
return 'Other'
df['Category'] = df['Description'].apply(categorise)
df['Amount'] = df['Amount'].abs()
# Summary by category
summary = df.groupby('Category')['Amount'].agg(['sum', 'count'])
summary.columns = ['Total', 'Transactions']
summary = summary.sort_values('Total', ascending=False)
print('\nExpense Summary')
print('=' * 40)
for cat, row in summary.iterrows():
print(f'{cat:20s} {row["Total"]:>8.2f} ({int(row["Transactions"])} txns)')
print(f'{"-" * 40}')
print(f'{'Total':20s} {summary["Total"].sum():>8.2f}')
# Save categorised data
df.to_csv('expenses_categorised.csv', index=False)
return summary
parse_expenses('bank_export.csv')3. Invoice PDF Generator
If you freelance or run a side business, generating invoices manually is tedious. This script creates clean PDF invoices with line items, VAT calculation, and payment terms. Feed it client details and items, and it outputs a professional PDF in seconds.
from fpdf import FPDF
from datetime import datetime, timedelta
def generate_invoice(client, items, invoice_number):
"""Generate a professional PDF invoice."""
pdf = FPDF()
pdf.add_page()
# Header
pdf.set_font('Helvetica', 'B', 24)
pdf.cell(0, 15, 'INVOICE', ln=True)
pdf.set_font('Helvetica', '', 10)
pdf.cell(0, 6, f'Invoice #: {invoice_number}', ln=True)
pdf.cell(0, 6, f'Date: {datetime.now().strftime("%d %B %Y")}', ln=True)
pdf.cell(0, 6, f'Due: {(datetime.now() + timedelta(days=30)).strftime("%d %B %Y")}', ln=True)
pdf.ln(10)
# Client details
pdf.set_font('Helvetica', 'B', 12)
pdf.cell(0, 8, 'Bill To:', ln=True)
pdf.set_font('Helvetica', '', 10)
pdf.cell(0, 6, client['name'], ln=True)
pdf.cell(0, 6, client['address'], ln=True)
pdf.cell(0, 6, client['email'], ln=True)
pdf.ln(10)
# Items table header
pdf.set_font('Helvetica', 'B', 10)
pdf.cell(90, 8, 'Description', border=1)
pdf.cell(30, 8, 'Quantity', border=1, align='C')
pdf.cell(35, 8, 'Unit Price', border=1, align='C')
pdf.cell(35, 8, 'Total', border=1, align='C')
pdf.ln()
# Items
pdf.set_font('Helvetica', '', 10)
subtotal = 0
for item in items:
total = item['qty'] * item['price']
subtotal += total
pdf.cell(90, 8, item['desc'], border=1)
pdf.cell(30, 8, str(item['qty']), border=1, align='C')
pdf.cell(35, 8, f'${item["price"]:.2f}', border=1, align='C')
pdf.cell(35, 8, f'${total:.2f}', border=1, align='C')
pdf.ln()
# Total
vat = subtotal * 0.20
pdf.ln(5)
pdf.set_font('Helvetica', '', 10)
pdf.cell(155, 8, 'Subtotal:', align='R')
pdf.cell(35, 8, f'${subtotal:.2f}', align='C')
pdf.ln()
pdf.cell(155, 8, 'VAT (20%):', align='R')
pdf.cell(35, 8, f'${vat:.2f}', align='C')
pdf.ln()
pdf.set_font('Helvetica', 'B', 12)
pdf.cell(155, 10, 'Total Due:', align='R')
pdf.cell(35, 10, f'${subtotal + vat:.2f}', align='C')
filename = f'invoice_{invoice_number}.pdf'
pdf.output(filename)
print(f'Invoice saved: {filename}')
# Usage
client = {
'name': 'Acme Ltd',
'address': '123 Business Road, London EC1A 1BB',
'email': '[email protected]'
}
items = [
{'desc': 'Python Development - 40 hours', 'qty': 40, 'price': 75.00},
{'desc': 'Data Pipeline Setup', 'qty': 1, 'price': 500.00},
{'desc': 'Monthly Hosting', 'qty': 1, 'price': 50.00},
]
generate_invoice(client, items, 'INV-2024-001')4. Stock Price Alerts
Set upper and lower price thresholds for stocks you are watching. When a price crosses your threshold, you get an email instantly. No need to stare at charts all day.
import yfinance as yf
import smtplib
from email.mime.text import MIMEText
import time
def check_price_alerts():
"""Check stocks against price thresholds."""
alerts = {
'AAPL': {'above': 200, 'below': 170},
'MSFT': {'above': 450, 'below': 380},
'TSLA': {'above': 300, 'below': 200},
}
triggered = []
for symbol, thresholds in alerts.items():
ticker = yf.Ticker(symbol)
price = ticker.info.get('currentPrice', 0)
if price > thresholds['above']:
triggered.append(
f'{symbol}: ${price:.2f} ABOVE ${thresholds["above"]}'
)
elif price < thresholds['below']:
triggered.append(
f'{symbol}: ${price:.2f} BELOW ${thresholds["below"]}'
)
if triggered:
body = 'Price alerts triggered:\n\n' + '\n'.join(triggered)
msg = MIMEText(body)
msg['Subject'] = f'Stock Alert - {len(triggered)} triggered'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login('[email protected]', 'your-app-password')
server.send_message(msg)
print(f'Alert sent: {len(triggered)} triggers')
else:
print('No alerts triggered')Scheduling Everything
The schedule library lets you run functions at specific times without cron jobs or task schedulers. For production use, consider running the script as a systemd service or inside a Docker container so it survives reboots.
import schedule
import time
def job_portfolio_email():
print('Sending portfolio email...')
send_daily_email()
def job_price_alerts():
print('Checking price alerts...')
check_price_alerts()
# Schedule the jobs
schedule.every().day.at('08:00').do(job_portfolio_email)
schedule.every(15).minutes.do(job_price_alerts)
schedule.every().monday.at('09:00').do(
lambda: parse_expenses('latest_export.csv')
)
print('Scheduler running. Press Ctrl+C to stop.')
while True:
schedule.run_pending()
time.sleep(60)Key Takeaways
- - Automate repetitive finance tasks so you can focus on decisions
- - Use Gmail app passwords (not real passwords) for email automation
- - yfinance provides free real-time stock data with a simple API
- - fpdf2 generates professional PDFs without complex dependencies
- - The schedule library handles timing without cron complexity
- - Start simple - one script that saves you 10 minutes a day adds up to 60+ hours a year