Files
discord-stock-bot/chart_generator.py
Michael Simard 71e70b77b0 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>
2025-12-25 00:25:00 -06:00

163 lines
5.4 KiB
Python

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