feat: add Freetrade portfolio tracker with price fetching and holdings CSV support

This commit is contained in:
Karl Hudgell 2026-04-20 11:38:27 +01:00
commit 38ecbd57e0
5 changed files with 96 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
venv/
__pycache__/
*.pyc
holdings.csv

52
README.md Normal file
View File

@ -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
}
]
```

3
holdings.sample.csv Normal file
View File

@ -0,0 +1,3 @@
GGP,GB,100
PAF,GB,200
SVM,US,50
1 GGP GB 100
2 PAF GB 200
3 SVM US 50

35
price.py Normal file
View File

@ -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))

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
requests
beautifulsoup4