Files
discord-stock-bot/polygon_api.py
Michael Simard 639abc71f6 Replace Yahoo Finance with Polygon.io for chart data
- Add Polygon.io API client for reliable historical data access
- Update bot to use Polygon instead of Yahoo Finance for charts
- Add POLYGON_API_KEY to config and environment example
- Polygon free tier: 5 API calls/minute, more reliable than Yahoo
- Fallback to FinViz chart URL when Polygon unavailable

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 11:48:42 -06:00

116 lines
3.6 KiB
Python

"""Polygon.io API client for historical stock data."""
import requests
import logging
from typing import Optional, Dict, List
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
class PolygonAPI:
"""Polygon.io implementation for historical stock data."""
def __init__(self, api_key: str):
"""
Initialize Polygon API client.
Args:
api_key: Polygon.io API key
"""
self.api_key = api_key
self.base_url = "https://api.polygon.io"
def get_candles(self, ticker: str, days: int = 30, resolution: str = 'D') -> Optional[Dict[str, List]]:
"""
Retrieve historical candlestick data from Polygon.io.
Args:
ticker: Stock ticker symbol
days: Number of days of historical data to fetch
resolution: Candle resolution (ignored, always daily)
Returns:
Dictionary with OHLCV data or None if unavailable
"""
try:
# Calculate date range
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
# Format dates as YYYY-MM-DD
from_date = start_date.strftime('%Y-%m-%d')
to_date = end_date.strftime('%Y-%m-%d')
# Build API URL
url = f"{self.base_url}/v2/aggs/ticker/{ticker}/range/1/day/{from_date}/{to_date}"
# Make request
params = {
'adjusted': 'true',
'sort': 'asc',
'apiKey': self.api_key
}
logger.info(f"Fetching Polygon data for {ticker} from {from_date} to {to_date}")
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# Check if we got results
if data.get('status') != 'OK' or not data.get('results'):
logger.warning(f"No candle data available for ticker: {ticker}")
return None
results = data['results']
# Convert to expected format
timestamps = []
opens = []
highs = []
lows = []
closes = []
volumes = []
for candle in results:
# Polygon returns timestamp in milliseconds
timestamps.append(int(candle['t'] / 1000))
opens.append(float(candle['o']))
highs.append(float(candle['h']))
lows.append(float(candle['l']))
closes.append(float(candle['c']))
volumes.append(int(candle['v']))
logger.info(f"Successfully fetched {len(timestamps)} candles for {ticker}")
return {
'timestamps': timestamps,
'open': opens,
'high': highs,
'low': lows,
'close': closes,
'volume': volumes
}
except requests.exceptions.RequestException as e:
logger.error(f"HTTP error fetching candle data for {ticker}: {e}")
return None
except Exception as e:
logger.error(f"Error fetching candle data for {ticker}: {e}")
return None
def is_available(self) -> bool:
"""
Check if Polygon API is accessible.
Returns:
True if accessible, False otherwise
"""
try:
# Test with a simple request
test_data = self.get_candles("AAPL", days=5)
return test_data is not None
except Exception as e:
logger.error(f"Polygon API unavailable: {e}")
return False