perf(price): parallelize price fetching with thread pool for faster processing
- Use ThreadPoolExecutor to fetch stock prices concurrently across multiple holdings - Pre-fetch FX rates for unique non-GBP currencies before parallel price fetching - Group price fetching and FX calculation into separate processing phases - Reduce I/O wait time by executing network requests in parallel instead of sequentially
This commit is contained in:
parent
f971708c5e
commit
ddec1e3683
60
price.py
60
price.py
@ -4,6 +4,7 @@ import csv
|
||||
import sys
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
FX_CACHE = {}
|
||||
|
||||
@ -18,6 +19,9 @@ def get_fx_rate(currency):
|
||||
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={
|
||||
@ -29,39 +33,37 @@ def get_price(market, ticker):
|
||||
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 = []
|
||||
holdings = []
|
||||
with open(holdings_file) as f:
|
||||
reader = csv.reader(f)
|
||||
for row in reader:
|
||||
ticker, market, shares = row[0].strip(), row[1].strip(), float(row[2].strip())
|
||||
ticker, market = row[0].strip(), row[1].strip()
|
||||
shares = 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,
|
||||
"fx_rate": fx,
|
||||
"total_value_gbp": total_gbp
|
||||
})
|
||||
holdings.append({"ticker": ticker, "market": market, "shares": shares, "currency": currency})
|
||||
|
||||
print(json.dumps(results, indent=2))
|
||||
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))
|
||||
Loading…
x
Reference in New Issue
Block a user