Initial commit: Discord stock bot with hourly PYPL updates
Functional Discord bot with automated hourly stock price updates during NYSE trading hours. Supports manual queries for any ticker via prefix commands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
121
market_hours.py
Normal file
121
market_hours.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from datetime import datetime, time
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MarketHours:
|
||||
"""
|
||||
Utility class for determining NYSE market hours.
|
||||
Market is open 9:30 AM - 4:00 PM ET, Monday-Friday (excluding holidays).
|
||||
"""
|
||||
|
||||
NYSE_TIMEZONE = pytz.timezone('America/New_York')
|
||||
MARKET_OPEN = time(9, 30)
|
||||
MARKET_CLOSE = time(16, 0)
|
||||
|
||||
# Major NYSE holidays (this is a simplified list - production systems would use a holiday calendar API)
|
||||
HOLIDAYS_2024 = [
|
||||
datetime(2024, 1, 1), # New Year's Day
|
||||
datetime(2024, 1, 15), # MLK Day
|
||||
datetime(2024, 2, 19), # Presidents Day
|
||||
datetime(2024, 3, 29), # Good Friday
|
||||
datetime(2024, 5, 27), # Memorial Day
|
||||
datetime(2024, 6, 19), # Juneteenth
|
||||
datetime(2024, 7, 4), # Independence Day
|
||||
datetime(2024, 9, 2), # Labor Day
|
||||
datetime(2024, 11, 28), # Thanksgiving
|
||||
datetime(2024, 12, 25), # Christmas
|
||||
]
|
||||
|
||||
HOLIDAYS_2025 = [
|
||||
datetime(2025, 1, 1), # New Year's Day
|
||||
datetime(2025, 1, 20), # MLK Day
|
||||
datetime(2025, 2, 17), # Presidents Day
|
||||
datetime(2025, 4, 18), # Good Friday
|
||||
datetime(2025, 5, 26), # Memorial Day
|
||||
datetime(2025, 6, 19), # Juneteenth
|
||||
datetime(2025, 7, 4), # Independence Day
|
||||
datetime(2025, 9, 1), # Labor Day
|
||||
datetime(2025, 11, 27), # Thanksgiving
|
||||
datetime(2025, 12, 25), # Christmas
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def is_market_open(cls, check_time: datetime = None) -> bool:
|
||||
"""
|
||||
Determine if the NYSE is currently open.
|
||||
|
||||
Args:
|
||||
check_time: Datetime to check (defaults to now)
|
||||
|
||||
Returns:
|
||||
True if market is open, False otherwise
|
||||
"""
|
||||
if check_time is None:
|
||||
check_time = datetime.now(cls.NYSE_TIMEZONE)
|
||||
else:
|
||||
check_time = check_time.astimezone(cls.NYSE_TIMEZONE)
|
||||
|
||||
# Check if weekend
|
||||
if check_time.weekday() >= 5: # Saturday = 5, Sunday = 6
|
||||
logger.debug("Market closed: Weekend")
|
||||
return False
|
||||
|
||||
# Check if holiday
|
||||
check_date = check_time.date()
|
||||
all_holidays = cls.HOLIDAYS_2024 + cls.HOLIDAYS_2025
|
||||
if any(holiday.date() == check_date for holiday in all_holidays):
|
||||
logger.debug(f"Market closed: Holiday ({check_date})")
|
||||
return False
|
||||
|
||||
# Check if within market hours
|
||||
current_time = check_time.time()
|
||||
if cls.MARKET_OPEN <= current_time < cls.MARKET_CLOSE:
|
||||
return True
|
||||
else:
|
||||
logger.debug(f"Market closed: Outside trading hours ({current_time})")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_next_market_open(cls, from_time: datetime = None) -> datetime:
|
||||
"""
|
||||
Calculate the next time the market will open.
|
||||
|
||||
Args:
|
||||
from_time: Starting datetime (defaults to now)
|
||||
|
||||
Returns:
|
||||
Datetime of next market open
|
||||
"""
|
||||
if from_time is None:
|
||||
from_time = datetime.now(cls.NYSE_TIMEZONE)
|
||||
else:
|
||||
from_time = from_time.astimezone(cls.NYSE_TIMEZONE)
|
||||
|
||||
# Start checking from the next day at market open
|
||||
next_day = from_time.replace(hour=cls.MARKET_OPEN.hour,
|
||||
minute=cls.MARKET_OPEN.minute,
|
||||
second=0,
|
||||
microsecond=0)
|
||||
|
||||
# If we have not passed today's open time, check today first
|
||||
if from_time.time() < cls.MARKET_OPEN:
|
||||
next_day = next_day
|
||||
else:
|
||||
# Otherwise start from tomorrow
|
||||
next_day = next_day.replace(day=next_day.day + 1)
|
||||
|
||||
# Find the next valid market day
|
||||
max_iterations = 14 # Search up to 2 weeks ahead
|
||||
for _ in range(max_iterations):
|
||||
if cls.is_market_open(next_day):
|
||||
return next_day
|
||||
# Move to next day
|
||||
next_day = next_day.replace(day=next_day.day + 1)
|
||||
|
||||
# Fallback (should not reach here under normal circumstances)
|
||||
logger.warning("Could not determine next market open within 2 weeks")
|
||||
return next_day
|
||||
Reference in New Issue
Block a user