Add matplotlib-based chart generation with Yahoo Finance data

- Add chart_generator.py with price and candlestick chart support
- Implement Yahoo Finance candle data fetching for free historical data
- Update bot to generate and attach charts to stock embeds
- Add matplotlib dependency to requirements.txt
- Configure dual API approach: Finnhub for quotes, Yahoo for charts

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Simard
2025-12-25 00:25:00 -06:00
parent 3040ab3cc1
commit 71e70b77b0
5 changed files with 290 additions and 8 deletions

162
chart_generator.py Normal file
View File

@@ -0,0 +1,162 @@
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
from typing import Dict, List, Optional
import logging
import io
logger = logging.getLogger(__name__)
class ChartGenerator:
"""Generates stock price charts using matplotlib."""
@staticmethod
def create_price_chart(
ticker: str,
candle_data: Dict[str, List],
company_name: Optional[str] = None
) -> Optional[io.BytesIO]:
"""
Create a price chart from candle data.
Args:
ticker: Stock ticker symbol
candle_data: Dictionary with timestamps, open, high, low, close, volume
company_name: Optional company name for title
Returns:
BytesIO buffer containing the chart image, or None if error
"""
try:
# Convert timestamps to datetime objects
dates = [datetime.fromtimestamp(ts) for ts in candle_data['timestamps']]
closes = candle_data['close']
# Create figure and axis
fig, ax = plt.subplots(figsize=(12, 6), facecolor='#1e1e1e')
ax.set_facecolor('#2d2d2d')
# Plot closing prices
ax.plot(dates, closes, color='#00d4ff', linewidth=2)
# Fill area under the line
ax.fill_between(dates, closes, alpha=0.3, color='#00d4ff')
# Format x-axis
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
plt.xticks(rotation=45, ha='right')
# Grid styling
ax.grid(True, alpha=0.2, color='#ffffff', linestyle='-', linewidth=0.5)
# Labels and title
title = f"{company_name} ({ticker})" if company_name else ticker
ax.set_title(title, color='#ffffff', fontsize=14, fontweight='bold', pad=20)
ax.set_xlabel('Date', color='#ffffff', fontsize=10)
ax.set_ylabel('Price ($)', color='#ffffff', fontsize=10)
# Tick colors
ax.tick_params(colors='#ffffff', which='both')
# Spine colors
for spine in ax.spines.values():
spine.set_color('#444444')
# Tight layout
plt.tight_layout()
# Save to buffer
buffer = io.BytesIO()
plt.savefig(buffer, format='png', facecolor='#1e1e1e', dpi=100)
buffer.seek(0)
plt.close(fig)
return buffer
except Exception as e:
logger.error(f"Error generating chart for {ticker}: {e}")
return None
@staticmethod
def create_candlestick_chart(
ticker: str,
candle_data: Dict[str, List],
company_name: Optional[str] = None
) -> Optional[io.BytesIO]:
"""
Create a candlestick chart from candle data.
Args:
ticker: Stock ticker symbol
candle_data: Dictionary with timestamps, open, high, low, close, volume
company_name: Optional company name for title
Returns:
BytesIO buffer containing the chart image, or None if error
"""
try:
# Convert timestamps to datetime objects
dates = [datetime.fromtimestamp(ts) for ts in candle_data['timestamps']]
opens = candle_data['open']
highs = candle_data['high']
lows = candle_data['low']
closes = candle_data['close']
# Create figure and axis
fig, ax = plt.subplots(figsize=(12, 6), facecolor='#1e1e1e')
ax.set_facecolor('#2d2d2d')
# Calculate bar width
width = 0.6
# Plot candlesticks
for i in range(len(dates)):
color = '#00ff88' if closes[i] >= opens[i] else '#ff4444'
# High-low line
ax.plot([dates[i], dates[i]], [lows[i], highs[i]], color=color, linewidth=1)
# Open-close rectangle
height = abs(closes[i] - opens[i])
bottom = min(opens[i], closes[i])
ax.bar(dates[i], height, width, bottom=bottom, color=color, alpha=0.8)
# Format x-axis
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
plt.xticks(rotation=45, ha='right')
# Grid styling
ax.grid(True, alpha=0.2, color='#ffffff', linestyle='-', linewidth=0.5)
# Labels and title
title = f"{company_name} ({ticker})" if company_name else ticker
ax.set_title(title, color='#ffffff', fontsize=14, fontweight='bold', pad=20)
ax.set_xlabel('Date', color='#ffffff', fontsize=10)
ax.set_ylabel('Price ($)', color='#ffffff', fontsize=10)
# Tick colors
ax.tick_params(colors='#ffffff', which='both')
# Spine colors
for spine in ax.spines.values():
spine.set_color('#444444')
# Tight layout
plt.tight_layout()
# Save to buffer
buffer = io.BytesIO()
plt.savefig(buffer, format='png', facecolor='#1e1e1e', dpi=100)
buffer.seek(0)
plt.close(fig)
return buffer
except Exception as e:
logger.error(f"Error generating candlestick chart for {ticker}: {e}")
return None