diff --git a/bot.py b/bot.py index bbfbe7b..2ab7a1f 100644 --- a/bot.py +++ b/bot.py @@ -169,9 +169,20 @@ class StockBot(commands.Bot): embed.add_field(name="Change", value=change_str, inline=True) embed.add_field(name="Previous Close", value=f"${stock_data['previous_close']}", inline=True) - # Add FinViz daily chart - chart_url = f"https://finviz.com/chart.ashx?t={ticker}&ty=c&ta=1&p=d&s=l" - embed.set_image(url=chart_url) + # Add 5-minute intraday chart from Finnhub + QuickChart + if hasattr(self.stock_api, 'get_intraday_candles'): + candles = self.stock_api.get_intraday_candles(ticker, resolution=5, days_back=1) + if candles: + chart_url = self.stock_api.generate_chart_url(ticker, candles) + embed.set_image(url=chart_url) + else: + # Fallback to FinViz daily chart if intraday data unavailable + chart_url = f"https://finviz.com/chart.ashx?t={ticker}&ty=c&ta=1&p=d&s=l" + embed.set_image(url=chart_url) + else: + # Fallback for non-Finnhub APIs + chart_url = f"https://finviz.com/chart.ashx?t={ticker}&ty=c&ta=1&p=d&s=l" + embed.set_image(url=chart_url) market_status = "🟢 Market Open" if stock_data['market_open'] else "🔴 Market Closed" embed.set_footer(text=f"{market_status}") diff --git a/stock_api/finnhub_api.py b/stock_api/finnhub_api.py index 102fa89..cc4383d 100644 --- a/stock_api/finnhub_api.py +++ b/stock_api/finnhub_api.py @@ -1,9 +1,11 @@ import finnhub import logging -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, List from .base import StockAPIBase -from datetime import datetime +from datetime import datetime, timedelta import pytz +import urllib.parse +import json logger = logging.getLogger(__name__) @@ -80,3 +82,94 @@ class FinnhubAPI(StockAPIBase): except Exception as e: logger.error(f"Finnhub API unavailable: {e}") return False + + def get_intraday_candles(self, ticker: str, resolution: int = 5, days_back: int = 1) -> Optional[List[Dict[str, Any]]]: + """ + Retrieve intraday candlestick data from Finnhub. + + Args: + ticker: Stock ticker symbol + resolution: Candle resolution in minutes (1, 5, 15, 30, 60) + days_back: Number of days of historical data to fetch + + Returns: + List of candle dictionaries or None if unavailable + """ + try: + # Calculate time range (Unix timestamps) + now = datetime.now(pytz.timezone('America/New_York')) + end_time = int(now.timestamp()) + start_time = int((now - timedelta(days=days_back)).timestamp()) + + # Fetch candle data + candles = self.client.stock_candles(ticker, resolution, start_time, end_time) + + if not candles or candles.get('s') != 'ok': + logger.warning(f"No candle data available for {ticker}") + return None + + # Format candles for chart consumption + formatted_candles = [] + for i in range(len(candles['t'])): + formatted_candles.append({ + 'x': candles['t'][i] * 1000, # Convert to milliseconds + 'o': round(candles['o'][i], 2), + 'h': round(candles['h'][i], 2), + 'l': round(candles['l'][i], 2), + 'c': round(candles['c'][i], 2) + }) + + return formatted_candles + + except Exception as e: + logger.error(f"Error fetching candles for {ticker}: {e}") + return None + + def generate_chart_url(self, ticker: str, candles: List[Dict[str, Any]]) -> str: + """ + Generate QuickChart URL for candlestick chart. + + Args: + ticker: Stock ticker symbol + candles: List of candle dictionaries with x, o, h, l, c keys + + Returns: + QuickChart URL string + """ + # Limit to last 50 candles for readability + chart_data = candles[-50:] if len(candles) > 50 else candles + + chart_config = { + 'type': 'candlestick', + 'data': { + 'datasets': [{ + 'label': f'{ticker} 5-Minute', + 'data': chart_data + }] + }, + 'options': { + 'plugins': { + 'title': { + 'display': True, + 'text': f'{ticker} - 5 Minute Chart' + } + }, + 'scales': { + 'x': { + 'type': 'time', + 'time': { + 'unit': 'minute', + 'displayFormats': { + 'minute': 'HH:mm' + } + } + } + } + } + } + + # Encode config for URL + config_json = json.dumps(chart_config) + encoded_config = urllib.parse.quote(config_json) + + return f"https://quickchart.io/chart?v=3&c={encoded_config}&width=800&height=400"