Initial commit: Discord stock price bot with hourly PYPL updates
Implemented Discord bot for automated stock price tracking and reporting with the following features: - Hourly PayPal (PYPL) stock price updates during NYSE market hours - Custom branding for PYPL as "Steaks Stablecoin Value" - Manual stock price queries via !stock and !price commands - Multi-provider stock API support (Yahoo Finance and Finnhub) - NYSE market hours detection with holiday awareness - Discord embed formatting with color-coded price changes - Docker containerization for consistent deployment - Comprehensive documentation and deployment guides Technical stack: - Python 3.9+ with discord.py - Finnhub API for stock price data - APScheduler for hourly automated updates - Docker support for local and Unraid deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
82
stock_api/finnhub_api.py
Normal file
82
stock_api/finnhub_api.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import finnhub
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from .base import StockAPIBase
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FinnhubAPI(StockAPIBase):
|
||||
"""Finnhub implementation of stock price provider."""
|
||||
|
||||
def __init__(self, api_key: str):
|
||||
"""
|
||||
Initialize Finnhub API client.
|
||||
|
||||
Args:
|
||||
api_key: Finnhub API key
|
||||
"""
|
||||
self.client = finnhub.Client(api_key=api_key)
|
||||
self.api_key = api_key
|
||||
|
||||
def get_stock_price(self, ticker: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Retrieve stock price data from Finnhub.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol
|
||||
|
||||
Returns:
|
||||
Dictionary with stock data or None if unavailable
|
||||
"""
|
||||
try:
|
||||
# Get current quote data
|
||||
quote = self.client.quote(ticker)
|
||||
|
||||
if not quote or quote.get('c') is None or quote.get('c') == 0:
|
||||
logger.warning(f"No data available for ticker: {ticker}")
|
||||
return None
|
||||
|
||||
current_price = float(quote['c']) # Current price
|
||||
previous_close = float(quote['pc']) # Previous close
|
||||
|
||||
if current_price == 0 or previous_close == 0:
|
||||
logger.warning(f"Invalid price data for ticker: {ticker}")
|
||||
return None
|
||||
|
||||
change_dollar = current_price - previous_close
|
||||
change_percent = (change_dollar / previous_close) * 100
|
||||
|
||||
# Use MarketHours utility for accurate market status
|
||||
from market_hours import MarketHours
|
||||
market_open = MarketHours.is_market_open()
|
||||
|
||||
return {
|
||||
'ticker': ticker.upper(),
|
||||
'current_price': round(current_price, 2),
|
||||
'previous_close': round(previous_close, 2),
|
||||
'change_dollar': round(change_dollar, 2),
|
||||
'change_percent': round(change_percent, 2),
|
||||
'market_open': market_open
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching data for {ticker}: {e}")
|
||||
return None
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""
|
||||
Check if Finnhub API is accessible.
|
||||
|
||||
Returns:
|
||||
True if accessible, False otherwise
|
||||
"""
|
||||
try:
|
||||
test_quote = self.client.quote("AAPL")
|
||||
return test_quote is not None and test_quote.get('c') is not None
|
||||
except Exception as e:
|
||||
logger.error(f"Finnhub API unavailable: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user