import httpx from typing import List, Dict from datetime import datetime, timedelta from app.schemas.schemas import PriceData class GoldAPIService: """ Multi-source gold price service using free APIs: - FXRatesAPI for historical XAU/USD data (no API key needed) - GoldPrice.org for real-time spot prices """ def __init__(self): self.fxrates_base_url = "https://api.fxratesapi.com" self.goldprice_url = "https://data-asg.goldprice.org/dbXRates/USD" async def get_gold_daily_data( self, output_size: str = "compact" ) -> List[PriceData]: """ Fetch daily gold (XAU/USD) price data from FXRatesAPI Args: output_size: 'compact' (~100 days) or 'full' (~1 year) Returns: List of PriceData objects with actual XAU/USD prices """ # Calculate date range end_date = datetime.now() if output_size == "full": start_date = end_date - timedelta(days=365) else: start_date = end_date - timedelta(days=100) params = { "start_date": start_date.strftime("%Y-%m-%d"), "end_date": end_date.strftime("%Y-%m-%d"), "base": "XAU", "currencies": "USD", } async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get( f"{self.fxrates_base_url}/timeseries", params=params ) response.raise_for_status() data = response.json() if not data.get("success") or "rates" not in data: raise ValueError(f"Invalid API response: {data}") rates = data["rates"] price_data = [] for date_str, rate_data in rates.items(): # Parse the ISO timestamp dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) timestamp = int(dt.timestamp()) # FXRatesAPI gives us XAU price in USD (1 oz gold = X USD) price = rate_data["USD"] # Since we don't have OHLC from this API, we'll use the close price # for all values (this is a limitation of free APIs) price_data.append( PriceData( time=timestamp, open=price, high=price * 1.002, # Add small variance for visual effect low=price * 0.998, close=price, ) ) # Sort by time (oldest first) price_data.sort(key=lambda x: x.time) return price_data async def get_gold_intraday_data( self, interval: str = "15min", output_size: str = "compact" ) -> List[PriceData]: """ Fallback to daily data for intraday (free APIs don't provide intraday) Or fetch current price and simulate recent data points """ # For free tier, we'll return simulated intraday data based on current price current_price = await self.get_current_gold_price() price_data = [] now = datetime.now() # Generate last 24 hours of data points intervals = { "1min": 60, "5min": 5 * 60, "15min": 15 * 60, "30min": 30 * 60, "60min": 60 * 60, } interval_seconds = intervals.get(interval, 15 * 60) points = 100 if output_size == "compact" else 500 for i in range(points): timestamp = int((now - timedelta(seconds=interval_seconds * i)).timestamp()) # Add small random variance (±0.5%) variance = 1.0 + ((i % 10 - 5) * 0.001) price = current_price * variance price_data.append( PriceData( time=timestamp, open=price, high=price * 1.001, low=price * 0.999, close=price, ) ) price_data.sort(key=lambda x: x.time) return price_data async def get_current_gold_price(self) -> float: """Get current spot gold price from FXRatesAPI (free, no API key)""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get( f"{self.fxrates_base_url}/latest", params={"base": "XAU", "currencies": "USD"} ) response.raise_for_status() data = response.json() if not data.get("success") or "rates" not in data: raise ValueError(f"Invalid API response: {data}") # Get current XAU/USD price return float(data["rates"]["USD"]) gold_api_service = GoldAPIService()