""" Financial Data Tool - Get stock quotes, crypto prices, and financial data Free sources used: - Yahoo Finance (yfinance library - completely free) - CoinGecko API (free tier: 10-50 calls/minute) - FRED API (Federal Reserve Economic Data - free with API key) - ExchangeRate-API (free tier) Most functions work without API keys. """ from __future__ import annotations import logging from datetime import datetime, timedelta from typing import Optional import requests log = logging.getLogger(__name__) # Free API endpoints COINGECKO_API = "https://api.coingecko.com/api/v3" EXCHANGE_RATE_API = "https://api.exchangerate-api.com/v4/latest" FRED_API = "https://api.stlouisfed.org/fred" def finance_get_stock_info( symbol: str, ) -> dict: """ Get stock information from Yahoo Finance. Args: symbol: Stock ticker symbol (e.g., AAPL, GOOGL, TSLA) Returns: Dictionary with stock information """ try: import yfinance as yf ticker = yf.Ticker(symbol.upper()) info = ticker.info # Extract key financial data result = { "success": True, "source": "yahoo_finance", "symbol": symbol.upper(), "company_name": info.get("longName", info.get("shortName", "")), "current_price": info.get("currentPrice") or info.get("regularMarketPrice"), "previous_close": info.get("previousClose"), "open": info.get("open"), "day_high": info.get("dayHigh"), "day_low": info.get("dayLow"), "52_week_high": info.get("fiftyTwoWeekHigh"), "52_week_low": info.get("fiftyTwoWeekLow"), "market_cap": info.get("marketCap"), "pe_ratio": info.get("trailingPE"), "forward_pe": info.get("forwardPE"), "dividend_yield": info.get("dividendYield"), "volume": info.get("volume"), "avg_volume": info.get("averageVolume"), "beta": info.get("beta"), "eps": info.get("trailingEps"), "revenue": info.get("totalRevenue"), "profit_margins": info.get("profitMargins"), "description": info.get("longBusinessSummary", "")[:1000], "sector": info.get("sector"), "industry": info.get("industry"), "website": info.get("website"), "timestamp": datetime.now().isoformat(), } # Remove None values result = {k: v for k, v in result.items() if v is not None} return result except ImportError: return { "success": False, "error": "yfinance not installed. Run: pip install yfinance", "source": "yahoo_finance", } except Exception as e: log.error(f"Stock info fetch failed: {e}") return { "success": False, "error": str(e), "source": "yahoo_finance", "symbol": symbol, } def finance_get_stock_history( symbol: str, period: str = "1mo", interval: str = "1d", ) -> dict: """ Get historical stock prices from Yahoo Finance. Args: symbol: Stock ticker symbol period: Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max) interval: Data interval (1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo) Returns: Dictionary with historical price data """ try: import yfinance as yf ticker = yf.Ticker(symbol.upper()) hist = ticker.history(period=period, interval=interval) if hist.empty: return { "success": False, "error": f"No historical data found for {symbol}", "source": "yahoo_finance", } # Convert to list of dicts prices = [] for index, row in hist.iterrows(): prices.append({ "date": index.isoformat(), "open": round(row["Open"], 2), "high": round(row["High"], 2), "low": round(row["Low"], 2), "close": round(row["Close"], 2), "volume": int(row["Volume"]), }) return { "success": True, "source": "yahoo_finance", "symbol": symbol.upper(), "period": period, "interval": interval, "prices": prices, "count": len(prices), } except ImportError: return { "success": False, "error": "yfinance not installed. Run: pip install yfinance", "source": "yahoo_finance", } except Exception as e: log.error(f"Stock history fetch failed: {e}") return { "success": False, "error": str(e), "source": "yahoo_finance", } def finance_get_crypto_price( coin_id: str = "bitcoin", vs_currency: str = "usd", ) -> dict: """ Get cryptocurrency price from CoinGecko. Args: coin_id: Coin ID (e.g., bitcoin, ethereum, dogecoin) - use coin name from CoinGecko vs_currency: Currency to show price in (e.g., usd, eur, btc) Returns: Dictionary with cryptocurrency data """ try: url = f"{COINGECKO_API}/simple/price" params = { "ids": coin_id.lower(), "vs_currencies": vs_currency.lower(), "include_market_cap": "true", "include_24hr_vol": "true", "include_24hr_change": "true", "include_last_updated_at": "true", } response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() if coin_id.lower() not in data: return { "success": False, "error": f"Coin not found: {coin_id}. Try using the full coin name (e.g., 'bitcoin' not 'btc')", "source": "coingecko", } coin_data = data[coin_id.lower()] return { "success": True, "source": "coingecko", "coin_id": coin_id.lower(), "currency": vs_currency.lower(), "price": coin_data.get(vs_currency.lower()), "market_cap": coin_data.get(f"{vs_currency.lower()}_market_cap"), "24h_volume": coin_data.get(f"{vs_currency.lower()}_24h_vol"), "24h_change": coin_data.get(f"{vs_currency.lower()}_24h_change"), "last_updated": datetime.fromtimestamp( coin_data.get("last_updated_at", 0) ).isoformat() if coin_data.get("last_updated_at") else None, } except Exception as e: log.error(f"Crypto price fetch failed: {e}") return { "success": False, "error": str(e), "source": "coingecko", } def finance_get_top_cryptos( limit: int = 10, vs_currency: str = "usd", ) -> dict: """ Get top cryptocurrencies by market cap from CoinGecko. Args: limit: Number of coins to return (default: 10) vs_currency: Currency for prices (default: usd) Returns: Dictionary with top cryptocurrencies """ try: url = f"{COINGECKO_API}/coins/markets" params = { "vs_currency": vs_currency.lower(), "order": "market_cap_desc", "per_page": limit, "page": 1, "sparkline": "false", } response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() results = [] for coin in data: results.append({ "id": coin.get("id"), "symbol": coin.get("symbol", "").upper(), "name": coin.get("name"), "price": coin.get("current_price"), "market_cap": coin.get("market_cap"), "market_cap_rank": coin.get("market_cap_rank"), "24h_change": coin.get("price_change_percentage_24h"), "volume": coin.get("total_volume"), "circulating_supply": coin.get("circulating_supply"), "image": coin.get("image"), }) return { "success": True, "source": "coingecko", "currency": vs_currency.lower(), "results": results, "count": len(results), } except Exception as e: log.error(f"Top cryptos fetch failed: {e}") return { "success": False, "error": str(e), "source": "coingecko", } def finance_get_exchange_rate( base_currency: str = "USD", target_currency: Optional[str] = None, ) -> dict: """ Get exchange rates from ExchangeRate-API (free). Args: base_currency: Base currency code (default: USD) target_currency: Target currency code (optional, returns all if not specified) Returns: Dictionary with exchange rate(s) """ try: url = f"https://api.exchangerate-api.com/v4/latest/{base_currency.upper()}" response = requests.get(url, timeout=10) response.raise_for_status() data = response.json() rates = data.get("rates", {}) if target_currency: target_currency = target_currency.upper() if target_currency in rates: return { "success": True, "source": "exchangerate-api", "base": base_currency.upper(), "target": target_currency, "rate": rates[target_currency], "last_updated": data.get("date"), } else: return { "success": False, "error": f"Currency not found: {target_currency}", "source": "exchangerate-api", } return { "success": True, "source": "exchangerate-api", "base": base_currency.upper(), "rates": rates, "count": len(rates), "last_updated": data.get("date"), } except Exception as e: log.error(f"Exchange rate fetch failed: {e}") return { "success": False, "error": str(e), "source": "exchangerate-api", } def finance_search_crypto( query: str, ) -> dict: """ Search for cryptocurrencies on CoinGecko. Args: query: Search query (coin name or symbol) Returns: Dictionary with search results """ try: url = f"{COINGECKO_API}/search" params = {"query": query} response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() coins = data.get("coins", [])[:10] results = [] for coin in coins: results.append({ "id": coin.get("id"), "symbol": coin.get("symbol", "").upper(), "name": coin.get("name"), "market_cap_rank": coin.get("market_cap_rank"), "thumb": coin.get("thumb"), }) return { "success": True, "source": "coingecko", "query": query, "results": results, "count": len(results), } except Exception as e: log.error(f"Crypto search failed: {e}") return { "success": False, "error": str(e), "source": "coingecko", } # Tool schemas for OpenAI function calling FINANCE_GET_STOCK_INFO_SCHEMA = { "type": "function", "function": { "name": "finance_get_stock_info", "description": "Get current stock information and key financial metrics from Yahoo Finance. Use for stock quotes and company data.", "parameters": { "type": "object", "properties": { "symbol": { "type": "string", "description": "Stock ticker symbol (e.g., AAPL, GOOGL, TSLA, MSFT)", }, }, "required": ["symbol"], }, }, } FINANCE_GET_STOCK_HISTORY_SCHEMA = { "type": "function", "function": { "name": "finance_get_stock_history", "description": "Get historical stock prices from Yahoo Finance. Use for price trends and charts.", "parameters": { "type": "object", "properties": { "symbol": { "type": "string", "description": "Stock ticker symbol", }, "period": { "type": "string", "description": "Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max)", "default": "1mo", }, "interval": { "type": "string", "description": "Data interval (1m, 5m, 15m, 1h, 1d, 1wk, 1mo)", "default": "1d", }, }, "required": ["symbol"], }, }, } FINANCE_GET_CRYPTO_PRICE_SCHEMA = { "type": "function", "function": { "name": "finance_get_crypto_price", "description": "Get cryptocurrency price and market data from CoinGecko. Use the full coin name (e.g., 'bitcoin' not 'btc').", "parameters": { "type": "object", "properties": { "coin_id": { "type": "string", "description": "CoinGecko coin ID (e.g., bitcoin, ethereum, dogecoin, solana)", }, "vs_currency": { "type": "string", "description": "Currency for price (default: usd)", "default": "usd", }, }, "required": ["coin_id"], }, }, } FINANCE_GET_TOP_CRYPTOS_SCHEMA = { "type": "function", "function": { "name": "finance_get_top_cryptos", "description": "Get top cryptocurrencies by market capitalization from CoinGecko.", "parameters": { "type": "object", "properties": { "limit": { "type": "integer", "description": "Number of coins to return (default: 10)", "default": 10, }, "vs_currency": { "type": "string", "description": "Currency for prices (default: usd)", "default": "usd", }, }, "required": [], }, }, } FINANCE_GET_EXCHANGE_RATE_SCHEMA = { "type": "function", "function": { "name": "finance_get_exchange_rate", "description": "Get currency exchange rates. Returns all rates for base currency or specific rate if target provided.", "parameters": { "type": "object", "properties": { "base_currency": { "type": "string", "description": "Base currency code (default: USD)", "default": "USD", }, "target_currency": { "type": "string", "description": "Target currency code (optional, returns all if not specified)", }, }, "required": [], }, }, } FINANCE_SEARCH_CRYPTO_SCHEMA = { "type": "function", "function": { "name": "finance_search_crypto", "description": "Search for cryptocurrencies on CoinGecko by name or symbol. Use this to find the correct coin_id for finance_get_crypto_price.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query (coin name or symbol)", }, }, "required": ["query"], }, }, }