From f971708c5e6ce18b58a40c8b514446c3be6624e8 Mon Sep 17 00:00:00 2001 From: Karl Hudgell Date: Mon, 20 Apr 2026 11:48:15 +0100 Subject: [PATCH] refactor(price): enhance currency handling with FX conversion and cache support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `get_fx_rate()` function with caching to fetch foreign exchange rates from open.er-api.com - Introduce `get_currency()` helper to map market codes to currency identifiers - Update price parsing regex to support multiple currency symbols (£, $, €) - Convert shares to float to accommodate fractional holdings - Add currency, fx_rate, and total_value_gbp fields to output JSON - Support fractional share counts (e.g., 3.6185 AAPL shares) --- holdings.sample.csv | 3 ++- price.py | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/holdings.sample.csv b/holdings.sample.csv index 6ee08ed..bb7507d 100644 --- a/holdings.sample.csv +++ b/holdings.sample.csv @@ -1,3 +1,4 @@ GGP,GB,100 PAF,GB,200 -SVM,US,50 \ No newline at end of file +SVM,US,50 +AAPL,US,3.6185 \ No newline at end of file diff --git a/price.py b/price.py index 130160e..e6d45a8 100644 --- a/price.py +++ b/price.py @@ -5,31 +5,63 @@ import sys import requests from bs4 import BeautifulSoup +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_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) + m = re.search(r"Latest price\s*:\s*[£$€]([0-9,.]+)", text) if m: return float(m.group(1).replace(",", "")) return None +def get_currency(market): + return {"GB": "GBP", "US": "USD"}.get(market, "GBP") + 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()) + ticker, market, shares = row[0].strip(), row[1].strip(), float(row[2].strip()) + currency = get_currency(market) price = get_price(market, ticker) + if price is None: + results.append({ + "ticker": ticker, + "market": market, + "currency": currency, + "shares": shares, + "price": None, + "fx_rate": None, + "total_value_gbp": None + }) + continue + fx = get_fx_rate(currency) + total_gbp = round(price * shares * fx, 2) results.append({ "ticker": ticker, "market": market, + "currency": currency, "shares": shares, "price": price, - "total_value": round(price * shares, 2) if price is not None else None + "fx_rate": fx, + "total_value_gbp": total_gbp }) print(json.dumps(results, indent=2)) \ No newline at end of file