refactor(price): enhance currency handling with FX conversion and cache support
- 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)
This commit is contained in:
parent
38ecbd57e0
commit
f971708c5e
@ -1,3 +1,4 @@
|
|||||||
GGP,GB,100
|
GGP,GB,100
|
||||||
PAF,GB,200
|
PAF,GB,200
|
||||||
SVM,US,50
|
SVM,US,50
|
||||||
|
AAPL,US,3.6185
|
||||||
|
38
price.py
38
price.py
@ -5,31 +5,63 @@ import sys
|
|||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
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):
|
def get_price(market, ticker):
|
||||||
url = f"https://web.freetrade.io/universe/{market}/{ticker}"
|
url = f"https://web.freetrade.io/universe/{market}/{ticker}"
|
||||||
response = requests.get(url, headers={
|
response = requests.get(url, headers={
|
||||||
"User-Agent": "Mozilla/5.0"
|
"User-Agent": "Mozilla/5.0"
|
||||||
}, timeout=20)
|
}, timeout=20)
|
||||||
text = BeautifulSoup(response.text, "html.parser").get_text(" ", strip=True)
|
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:
|
if m:
|
||||||
return float(m.group(1).replace(",", ""))
|
return float(m.group(1).replace(",", ""))
|
||||||
return None
|
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"
|
holdings_file = sys.argv[1] if len(sys.argv) > 1 else "holdings.csv"
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
with open(holdings_file) as f:
|
with open(holdings_file) as f:
|
||||||
reader = csv.reader(f)
|
reader = csv.reader(f)
|
||||||
for row in reader:
|
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)
|
price = get_price(market, ticker)
|
||||||
|
if price is None:
|
||||||
results.append({
|
results.append({
|
||||||
"ticker": ticker,
|
"ticker": ticker,
|
||||||
"market": market,
|
"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,
|
"shares": shares,
|
||||||
"price": price,
|
"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))
|
print(json.dumps(results, indent=2))
|
||||||
Loading…
x
Reference in New Issue
Block a user