69 lines
2.1 KiB
Python
Raw Permalink Normal View History

import re
import json
import csv
import sys
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, as_completed
FX_CACHE = {}
def get_fx_rate(currency):
if currency in FX_CACHE:
return FX_CACHE[currency]
if currency == "GBP":
return 1.0
url = f"https://open.er-api.com/v6/latest/{currency}"
r = requests.get(url, timeout=20)
rate = r.json()["rates"]["GBP"]
FX_CACHE[currency] = rate
return rate
def get_currency(market):
return {"GB": "GBP", "US": "USD"}.get(market, "GBP")
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"
holdings = []
with open(holdings_file) as f:
reader = csv.reader(f)
for row in reader:
2026-04-20 12:32:55 +01:00
name, ticker, market = row[0].strip(), row[1].strip(), row[2].strip()
shares = float(row[3].strip())
currency = get_currency(market)
2026-04-20 12:32:55 +01:00
holdings.append({"name": name, "ticker": ticker, "market": market, "shares": shares, "currency": currency})
unique_currencies = {h["currency"] for h in holdings if h["currency"] != "GBP"}
for currency in unique_currencies:
get_fx_rate(currency)
with ThreadPoolExecutor(max_workers=10) as pool:
future_to_idx = {
pool.submit(get_price, h["market"], h["ticker"]): i
for i, h in enumerate(holdings)
}
for future in as_completed(future_to_idx):
idx = future_to_idx[future]
holdings[idx]["price"] = future.result()
for h in holdings:
if h["price"] is not None:
fx = FX_CACHE.get(h["currency"], 1.0)
h["fx_rate"] = fx
h["total_value_gbp"] = round(h["price"] * h["shares"] * fx, 2)
else:
h["fx_rate"] = None
h["total_value_gbp"] = None
print(json.dumps(holdings, indent=2))