| """ |
| Live gold (USD / troy oz) via yfinance, aligned with api_key_test.py (GC=F). |
| """ |
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from typing import List, Optional, Tuple |
|
|
|
|
| @dataclass |
| class GoldPriceQuote: |
| usd_per_oz: float |
| source: str |
|
|
|
|
| def os_gold_symbol() -> str: |
| import os |
|
|
| return (os.environ.get("SHOPMANAGER_GOLD_SYMBOL", "GC=F") or "GC=F").strip() |
|
|
|
|
| def _fetch_yfinance_gold() -> Tuple[float, str, List[float]]: |
| import yfinance as yf |
|
|
| sym = (os_gold_symbol() or "GC=F").strip() or "GC=F" |
| ticker = yf.Ticker(sym) |
| hist = ticker.history(period="60d", interval="1d") |
| if hist is None or hist.empty: |
| raise ValueError("No price history for gold symbol") |
| closes = hist["Close"].dropna().astype(float).tolist() |
| if not closes: |
| raise ValueError("Empty close series for gold") |
| return float(closes[-1]), f"yfinance:{sym}", [float(c) for c in closes[-30:]] |
|
|
|
|
| def fetch_gold_spot_usd_per_oz() -> GoldPriceQuote: |
| usd, src, _ = _fetch_yfinance_gold() |
| if usd <= 0: |
| raise ValueError("Invalid non-positive gold price") |
| return GoldPriceQuote(usd_per_oz=usd, source=src) |
|
|
|
|
| def recent_close_history(max_points: int = 30) -> List[float]: |
| try: |
| _, _, hist = _fetch_yfinance_gold() |
| except Exception: |
| return [] |
| if max_points and len(hist) > max_points: |
| return hist[-max_points:] |
| return list(hist) |
|
|
|
|
| def last_quote_or_fallback(fallback: float) -> GoldPriceQuote: |
| try: |
| return fetch_gold_spot_usd_per_oz() |
| except Exception: |
| return GoldPriceQuote(usd_per_oz=fallback, source="fallback") |
|
|