Update stock_manager.py

removing logos to focus on function for now
This commit is contained in:
Chuck
2025-04-08 21:18:12 -05:00
parent 04cfa86430
commit e87251caf4

View File

@@ -19,151 +19,69 @@ class StockManager:
self.last_update = 0 self.last_update = 0
self.stock_data = {} self.stock_data = {}
self.current_stock_index = 0 self.current_stock_index = 0
self.base_url = "https://query2.finance.yahoo.com" self.base_url = "https://query1.finance.yahoo.com"
self.logo_cache = {} self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
# Set logos directory path (assuming it exists) }
self.logos_dir = "assets/logos/stocks"
# Default colors for stocks
self.default_colors = [
(0, 255, 0), # Green
(0, 255, 255), # Cyan
(255, 255, 0), # Yellow
(255, 165, 0), # Orange
(128, 0, 128), # Purple
(255, 0, 0), # Red
(0, 0, 255), # Blue
(255, 192, 203) # Pink
]
def _get_stock_color(self, symbol: str) -> Tuple[int, int, int]: def _get_stock_color(self, symbol: str) -> Tuple[int, int, int]:
"""Get a consistent color for a stock symbol.""" """Get color based on stock performance."""
# Use the symbol as a seed for consistent color assignment if symbol not in self.stock_data:
random.seed(hash(symbol)) return (255, 255, 255) # White for unknown
color_index = random.randint(0, len(self.default_colors) - 1)
random.seed() # Reset the seed change = self.stock_data[symbol].get('change', 0)
return self.default_colors[color_index] if change > 0:
return (0, 255, 0) # Green for positive
elif change < 0:
return (255, 0, 0) # Red for negative
return (255, 255, 0) # Yellow for no change
def _fetch_stock_data(self, symbol: str) -> Dict[str, Any]: def _fetch_stock_data(self, symbol: str) -> Dict[str, Any]:
"""Fetch stock data from Yahoo Finance API.""" """Fetch stock data from Yahoo Finance API."""
try: try:
# Use Yahoo Finance API directly # Use Yahoo Finance quote endpoint
url = f"{self.base_url}/v8/finance/chart/{symbol}" url = f"{self.base_url}/v7/finance/quote"
params = { params = {
"interval": "1m", "symbols": symbol
"period": "1d"
} }
response = requests.get(url, params=params)
response = requests.get(url, params=params, headers=self.headers)
response.raise_for_status() # Raise an error for bad status codes
data = response.json() data = response.json()
logger.debug(f"Raw response for {symbol}: {data}")
if "chart" not in data or "result" not in data["chart"] or not data["chart"]["result"]: if not data or "quoteResponse" not in data or "result" not in data["quoteResponse"] or not data["quoteResponse"]["result"]:
logger.error(f"Invalid response for {symbol}: {data}") logger.error(f"Invalid response format for {symbol}")
return None return None
result = data["chart"]["result"][0]
meta = result["meta"]
# Extract price data quote = data["quoteResponse"]["result"][0]
price = meta.get("regularMarketPrice", 0)
prev_close = meta.get("chartPreviousClose", 0)
# Calculate change percentage # Extract required fields with fallbacks
change_pct = 0 price = quote.get("regularMarketPrice", 0)
if prev_close > 0: prev_close = quote.get("regularMarketPreviousClose", price)
change_pct = quote.get("regularMarketChangePercent", 0)
# If we didn't get a change percentage, calculate it
if change_pct == 0 and prev_close != 0:
change_pct = ((price - prev_close) / prev_close) * 100 change_pct = ((price - prev_close) / prev_close) * 100
# Get company name if available
company_name = meta.get("instrumentInfo", {}).get("longName", symbol)
return { return {
"symbol": symbol, "symbol": symbol,
"name": company_name, "name": quote.get("longName", symbol),
"price": price, "price": price,
"change": change_pct, "change": change_pct,
"open": prev_close "open": prev_close
} }
except Exception as e:
logger.error(f"Error fetching stock data for {symbol}: {e}") except requests.exceptions.RequestException as e:
logger.error(f"Network error fetching data for {symbol}: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"JSON decode error for {symbol}: {e}")
return None return None
def _download_logo(self, symbol: str) -> str:
"""Download company logo for a stock symbol."""
logo_path = os.path.join(self.logos_dir, f"{symbol}.png")
# If logo already exists, return the path
if os.path.exists(logo_path):
return logo_path
try:
# Try to get logo from Yahoo Finance
url = f"https://query2.finance.yahoo.com/v7/finance/options/{symbol}"
response = requests.get(url)
data = response.json()
if "optionChain" in data and "result" in data["optionChain"] and data["optionChain"]["result"]:
result = data["optionChain"]["result"][0]
if "quote" in result and "logoUrl" in result["quote"]:
logo_url = result["quote"]["logoUrl"]
# Download the logo
logo_response = requests.get(logo_url)
if logo_response.status_code == 200:
try:
with open(logo_path, "wb") as f:
f.write(logo_response.content)
logger.info(f"Downloaded logo for {symbol}")
return logo_path
except IOError as e:
logger.error(f"Could not write logo file for {symbol}: {e}")
return None
# If we couldn't get a logo, create a placeholder
return self._create_placeholder_logo(symbol, logo_path)
except Exception as e: except Exception as e:
logger.error(f"Error downloading logo for {symbol}: {e}") logger.error(f"Unexpected error fetching data for {symbol}: {e}")
# Create a placeholder logo
return self._create_placeholder_logo(symbol, logo_path)
def _create_placeholder_logo(self, symbol: str, logo_path: str) -> str:
"""Create a placeholder logo with the stock symbol."""
try:
from PIL import Image, ImageDraw, ImageFont
# Create a 32x32 image with a colored background
color = self._get_stock_color(symbol)
img = Image.new('RGB', (32, 32), color)
draw = ImageDraw.Draw(img)
# Try to load a font, fall back to default if not available
try:
font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8)
except:
font = ImageFont.load_default()
# Draw the symbol
text_bbox = draw.textbbox((0, 0), symbol, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
# Center the text
x = (32 - text_width) // 2
y = (32 - text_height) // 2
draw.text((x, y), symbol, fill=(255, 255, 255), font=font)
try:
# Save the image
img.save(logo_path)
logger.info(f"Created placeholder logo for {symbol}")
return logo_path
except IOError as e:
logger.error(f"Could not save placeholder logo for {symbol}: {e}")
return None
except Exception as e:
logger.error(f"Error creating placeholder logo for {symbol}: {e}")
return None return None
def update_stock_data(self): def update_stock_data(self):
@@ -174,29 +92,27 @@ class StockManager:
# Get symbols from config # Get symbols from config
symbols = self.stocks_config.get('symbols', []) symbols = self.stocks_config.get('symbols', [])
if not symbols:
logger.warning("No stock symbols configured")
return
# If symbols is a list of strings, convert to list of dicts # If symbols is a list of strings, convert to list of dicts
if symbols and isinstance(symbols[0], str): if isinstance(symbols[0], str):
symbols = [{"symbol": symbol} for symbol in symbols] symbols = [{"symbol": symbol} for symbol in symbols]
success = False # Track if we got any successful updates
for stock in symbols: for stock in symbols:
symbol = stock['symbol'] symbol = stock['symbol']
data = self._fetch_stock_data(symbol) data = self._fetch_stock_data(symbol)
if data: if data:
# Add color if not specified
if 'color' not in stock:
data['color'] = self._get_stock_color(symbol)
else:
data['color'] = tuple(stock['color'])
# Try to get logo
logo_path = self._download_logo(symbol)
if logo_path:
data['logo_path'] = logo_path
self.stock_data[symbol] = data self.stock_data[symbol] = data
success = True
logger.info(f"Updated {symbol}: ${data['price']:.2f} ({data['change']:+.2f}%)")
self.last_update = current_time if success:
self.last_update = current_time
else:
logger.error("Failed to fetch data for any configured stocks")
def display_stocks(self, force_clear: bool = False): def display_stocks(self, force_clear: bool = False):
"""Display stock information on the LED matrix.""" """Display stock information on the LED matrix."""