feat(trading): add trading day utilities
This commit is contained in:
parent
fed83c07f9
commit
3ee3372203
@ -1,31 +1,72 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List
|
from typing import List
|
||||||
|
import pandas_market_calendars as mcal
|
||||||
|
|
||||||
|
|
||||||
|
NYSE = mcal.get_calendar("NYSE")
|
||||||
|
|
||||||
|
|
||||||
def is_trading_day(date: datetime) -> bool:
|
def is_trading_day(date: datetime) -> bool:
|
||||||
return date.weekday() not in [5, 6]
|
valid_days = NYSE.valid_days(
|
||||||
|
start_date=date.strftime("%Y-%m-%d"), end_date=date.strftime("%Y-%m-%d")
|
||||||
|
)
|
||||||
|
|
||||||
|
return len(valid_days) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_trading_days(start_date: datetime, end_date: datetime) -> List[datetime]:
|
||||||
|
valid_days = NYSE.valid_days(
|
||||||
|
start_date=start_date.strftime("%Y-%m-%d"),
|
||||||
|
end_date=end_date.strftime("%Y-%m-%d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [day.to_pydatetime().replace(tzinfo=None) for day in valid_days]
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_n_trading_days(date: datetime, n: int) -> List[datetime]:
|
||||||
|
"""
|
||||||
|
Get the last N trading days before (and including) the given date.
|
||||||
|
"""
|
||||||
|
# Add buffer for weekends/holidays (n trading days ≈ n * 1.5 calendar days)
|
||||||
|
buffer_days = int(n * 2)
|
||||||
|
start_date = date - timedelta(days=buffer_days)
|
||||||
|
|
||||||
|
# Get all trading days in the range
|
||||||
|
trading_days = get_trading_days(start_date, date)
|
||||||
|
|
||||||
|
# Return the last n trading days
|
||||||
|
return trading_days[-n:] if len(trading_days) >= n else trading_days
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_trading_day(date: datetime) -> datetime:
|
||||||
|
next_day = date + timedelta(days=1)
|
||||||
|
|
||||||
|
# Search up to 10 days ahead (handles long holiday weekends)
|
||||||
|
for i in range(10):
|
||||||
|
check_date = next_day + timedelta(days=i)
|
||||||
|
if is_trading_day(check_date):
|
||||||
|
return check_date
|
||||||
|
|
||||||
|
raise ValueError(f"No trading day found within 10 days of {date}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_previous_trading_day(date: datetime) -> datetime:
|
||||||
|
prev_day = date - timedelta(days=1)
|
||||||
|
|
||||||
|
# Search up to 10 days back
|
||||||
|
for i in range(10):
|
||||||
|
check_date = prev_day - timedelta(days=i)
|
||||||
|
if is_trading_day(check_date):
|
||||||
|
return check_date
|
||||||
|
|
||||||
|
raise ValueError(f"No trading day found within 10 days before {date}")
|
||||||
|
|
||||||
|
|
||||||
def parse_date_yyyymmdd(date_str: str) -> datetime:
|
def parse_date_yyyymmdd(date_str: str) -> datetime:
|
||||||
|
"""Parse date string in YYYYMMDD format to datetime."""
|
||||||
return datetime.strptime(date_str, "%Y%m%d")
|
return datetime.strptime(date_str, "%Y%m%d")
|
||||||
|
|
||||||
def days_range_from(date: datetime, n: int) -> List[datetime]:
|
|
||||||
"""
|
|
||||||
Generate a list of dates going back n days from the given date (inclusive).
|
|
||||||
|
|
||||||
Example: date=Jan 3, n=2 → [Jan 1, Jan 2, Jan 3]
|
|
||||||
|
|
||||||
Args:
|
|
||||||
date: The target date
|
|
||||||
n: Number of days to backtrack
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of datetime objects from (date - n) to date (inclusive)
|
|
||||||
"""
|
|
||||||
start_date = date - timedelta(days=n)
|
|
||||||
|
|
||||||
return [start_date + timedelta(days=i) for i in range(n + 1)]
|
|
||||||
|
|
||||||
|
|
||||||
def format_date_readable(date: datetime) -> str:
|
def format_date_readable(date: datetime) -> str:
|
||||||
|
"""Format datetime to readable string (e.g., 'October 15, 2025')."""
|
||||||
return date.strftime("%B %d, %Y")
|
return date.strftime("%B %d, %Y")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user