TradingAgents/tradingagents/clients/finnhub_client.py

239 lines
7.5 KiB
Python

"""
Finnhub client for financial data access.
"""
import logging
from datetime import date
from typing import Any
import finnhub
logger = logging.getLogger(__name__)
class FinnhubClient:
"""
Finnhub API client for accessing financial data including fundamental data.
Provides access to:
- Company news
- Insider transactions
- Insider sentiment
- Real-time quotes
- Company profiles
- Financial statements (balance sheet, income statement, cash flow)
"""
def __init__(self, api_key: str):
"""
Initialize Finnhub client with official SDK.
Args:
api_key: Finnhub API key
"""
self.api_key = api_key
self.client = finnhub.Client(api_key=api_key)
def test_connection(self) -> bool:
"""Test if the Finnhub API connection is working."""
try:
# Test with a simple quote request
response = self.client.quote("AAPL")
return "c" in response # 'c' is current price field
except Exception as e:
logger.error(f"Finnhub connection test failed: {e}")
return False
def get_balance_sheet(
self, symbol: str, frequency: str, report_date: date
) -> dict[str, Any]:
"""
Get balance sheet data from Finnhub.
Args:
symbol: Stock symbol (e.g., 'AAPL')
frequency: Reporting frequency ('quarterly' or 'annual')
report_date: Report date as date object
Returns:
Balance sheet data from Finnhub API
"""
try:
# Finnhub SDK expects frequency as 'quarterly' or 'annual'
freq = "quarterly" if frequency.lower() in ["quarterly", "q"] else "annual"
response = self.client.financials_reported(symbol=symbol.upper(), freq=freq)
return response if isinstance(response, dict) else {"data": []}
except Exception as e:
logger.error(f"Error fetching balance sheet for {symbol}: {e}")
return {"data": []}
def get_income_statement(
self, symbol: str, frequency: str, report_date: date
) -> dict[str, Any]:
"""
Get income statement data from Finnhub.
Args:
symbol: Stock symbol (e.g., 'AAPL')
frequency: Reporting frequency ('quarterly' or 'annual')
report_date: Report date as date object
Returns:
Income statement data from Finnhub API
"""
try:
freq = "quarterly" if frequency.lower() in ["quarterly", "q"] else "annual"
response = self.client.financials_reported(symbol=symbol.upper(), freq=freq)
return response if isinstance(response, dict) else {"data": []}
except Exception as e:
logger.error(f"Error fetching income statement for {symbol}: {e}")
return {"data": []}
def get_cash_flow(
self, symbol: str, frequency: str, report_date: date
) -> dict[str, Any]:
"""
Get cash flow statement data from Finnhub.
Args:
symbol: Stock symbol (e.g., 'AAPL')
frequency: Reporting frequency ('quarterly' or 'annual')
report_date: Report date as date object
Returns:
Cash flow statement data from Finnhub API
"""
try:
freq = "quarterly" if frequency.lower() in ["quarterly", "q"] else "annual"
response = self.client.financials_reported(symbol=symbol.upper(), freq=freq)
return response if isinstance(response, dict) else {"data": []}
except Exception as e:
logger.error(f"Error fetching cash flow for {symbol}: {e}")
return {"data": []}
def get_company_news(
self, symbol: str, start_date: date, end_date: date
) -> list[dict[str, Any]]:
"""
Get company news for a specific symbol and date range.
Args:
symbol: Stock symbol (e.g., 'AAPL')
start_date: Start date as date object
end_date: End date as date object
Returns:
List of news articles
"""
# Convert date objects to strings for API
start_str = start_date.isoformat()
end_str = end_date.isoformat()
try:
response = self.client.company_news(
symbol.upper(), _from=start_str, to=end_str
)
return response if isinstance(response, list) else []
except Exception as e:
logger.error(f"Error fetching news for {symbol}: {e}")
return []
def get_insider_transactions(
self, symbol: str, start_date: date, end_date: date
) -> dict[str, Any]:
"""
Get insider transactions for a company.
Args:
symbol: Stock symbol
start_date: Start date as date object
end_date: End date as date object
Returns:
Insider transaction data
"""
start_str = start_date.isoformat()
end_str = end_date.isoformat()
try:
response = self.client.stock_insider_transactions(
symbol.upper(), _from=start_str, to=end_str
)
return response if isinstance(response, dict) else {"data": []}
except Exception as e:
logger.error(f"Error fetching insider transactions for {symbol}: {e}")
return {"data": []}
def get_insider_sentiment(
self, symbol: str, start_date: date, end_date: date
) -> dict[str, Any]:
"""
Get insider sentiment data for a company.
Args:
symbol: Stock symbol
start_date: Start date as date object
end_date: End date as date object
Returns:
Insider sentiment data
"""
start_str = start_date.isoformat()
end_str = end_date.isoformat()
try:
response = self.client.stock_insider_sentiment(
symbol.upper(), _from=start_str, to=end_str
)
return response if isinstance(response, dict) else {"data": []}
except Exception as e:
logger.error(f"Error fetching insider sentiment for {symbol}: {e}")
return {"data": []}
def get_quote(self, symbol: str) -> dict[str, Any]:
"""
Get current quote for a symbol.
Args:
symbol: Stock symbol
Returns:
Quote data with current price, change, etc.
"""
try:
response = self.client.quote(symbol.upper())
return response if isinstance(response, dict) else {}
except Exception as e:
logger.error(f"Error fetching quote for {symbol}: {e}")
return {}
def get_company_profile(self, symbol: str) -> dict[str, Any]:
"""
Get company profile information.
Args:
symbol: Stock symbol
Returns:
Company profile data
"""
try:
response = self.client.company_profile2(symbol=symbol.upper())
return response if isinstance(response, dict) else {}
except Exception as e:
logger.error(f"Error fetching company profile for {symbol}: {e}")
return {}
def get_client_info(self) -> dict[str, Any]:
"""
Get information about this client.
Returns:
Dict[str, Any]: Client metadata
"""
return {
"client_type": self.__class__.__name__,
"api_key_set": bool(self.api_key),
"sdk_version": getattr(finnhub, "__version__", "unknown"),
}