commit 38ecbd57e0b5ae976499416379508c506a34c27d Author: Karl Hudgell Date: Mon Apr 20 11:38:27 2026 +0100 feat: add Freetrade portfolio tracker with price fetching and holdings CSV support diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c34d9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +venv/ +__pycache__/ +*.pyc +holdings.csv \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fad3ab3 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Freetrade Portfolio Tracker + +Fetches live stock prices from Freetrade and calculates portfolio holdings values. + +## Setup + +```bash +python -m venv venv +venv\Scripts\activate # Windows +source venv/bin/activate # Linux/Mac + +pip install requests beautifulsoup4 +``` + +## Usage + +Copy the sample file and add your own positions: + +```bash +cp holdings.sample.csv holdings.csv +``` + +Format (`ticker,market,shares`): + +```csv +GGP,GB,100 +PAF,GB,200 +SVM,US,50 +``` + +Markets: `GB` for UK stocks, `US` for US-listed stocks. + +Run: + +```bash +python price.py # uses holdings.csv by default +python price.py my_portfolio.csv # custom file +``` + +Output (JSON): + +```json +[ + { + "ticker": "GGP", + "market": "GB", + "shares": 100, + "price": 7.67, + "total_value": 767.0 + } +] +``` \ No newline at end of file diff --git a/holdings.sample.csv b/holdings.sample.csv new file mode 100644 index 0000000..6ee08ed --- /dev/null +++ b/holdings.sample.csv @@ -0,0 +1,3 @@ +GGP,GB,100 +PAF,GB,200 +SVM,US,50 \ No newline at end of file diff --git a/price.py b/price.py new file mode 100644 index 0000000..130160e --- /dev/null +++ b/price.py @@ -0,0 +1,35 @@ +import re +import json +import csv +import sys +import requests +from bs4 import BeautifulSoup + +def get_price(market, ticker): + url = f"https://web.freetrade.io/universe/{market}/{ticker}" + response = requests.get(url, headers={ + "User-Agent": "Mozilla/5.0" + }, timeout=20) + text = BeautifulSoup(response.text, "html.parser").get_text(" ", strip=True) + m = re.search(r"Latest price\s*:\s*[£$]([0-9,.]+)", text) + if m: + return float(m.group(1).replace(",", "")) + return None + +holdings_file = sys.argv[1] if len(sys.argv) > 1 else "holdings.csv" + +results = [] +with open(holdings_file) as f: + reader = csv.reader(f) + for row in reader: + ticker, market, shares = row[0].strip(), row[1].strip(), int(row[2].strip()) + price = get_price(market, ticker) + results.append({ + "ticker": ticker, + "market": market, + "shares": shares, + "price": price, + "total_value": round(price * shares, 2) if price is not None else None + }) + +print(json.dumps(results, indent=2)) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a98ae43 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +beautifulsoup4 \ No newline at end of file