CodeForFinance
← Back to Tutorials

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 pandas

1. 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

Developer Essentials

As an Amazon Associate we may earn from qualifying purchases.