From 50d26aad40e0e4161084043b29ddb080135ea523 Mon Sep 17 00:00:00 2001 From: Ali Behjati <bahjatia@gmail.com> Date: Fri, 20 Dec 2024 14:35:05 +0100 Subject: [PATCH] feat!: add market schedule This change removes the hard-coded calendar and adds MarketSchedule class to offer the same features using `schedule` metadata in the products. feat!: add market schedule This change removes the hard-coded calendar and adds MarketSchedule class to offer the same features using `schedule` metadata in the products. --- pythclient/calendar.py | 440 -------- pythclient/calendar_full_intervals.py | 1481 ------------------------- pythclient/market_schedule.py | 209 ++++ pythclient/pythaccounts.py | 9 + setup.py | 2 +- tests/test_calendar.py | 489 -------- tests/test_market_schedule.py | 152 +++ 7 files changed, 371 insertions(+), 2411 deletions(-) delete mode 100644 pythclient/calendar.py delete mode 100644 pythclient/calendar_full_intervals.py create mode 100644 pythclient/market_schedule.py delete mode 100644 tests/test_calendar.py create mode 100644 tests/test_market_schedule.py diff --git a/pythclient/calendar.py b/pythclient/calendar.py deleted file mode 100644 index 3d07e6c..0000000 --- a/pythclient/calendar.py +++ /dev/null @@ -1,440 +0,0 @@ -import datetime -from zoneinfo import ZoneInfo - -NY_TZ = ZoneInfo("America/New_York") -UTC_TZ = ZoneInfo("UTC") - -EQUITY_OPEN = datetime.time(9, 30, 0, tzinfo=NY_TZ) -EQUITY_CLOSE = datetime.time(16, 0, 0, tzinfo=NY_TZ) - -NYSE_EARLY_CLOSE = datetime.time(13, 0, 0, tzinfo=NY_TZ) - -# NYSE_HOLIDAYS and NYSE_EARLY_HOLIDAYS will need to be updated each year -# From https://www.nyse.com/markets/hours-calendars -NYSE_HOLIDAYS = [ - datetime.datetime(2023, 1, 2, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 1, 16, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 2, 20, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 4, 7, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 5, 29, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 6, 19, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 7, 4, tzinfo=NY_TZ).date(), - datetime.datetime(2022, 9, 4, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 11, 23, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 12, 25, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 1, 1, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 1, 15, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 2, 19, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 3, 29, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 5, 27, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 6, 19, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 7, 4, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 9, 2, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 11, 28, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 12, 25, tzinfo=NY_TZ).date(), -] -NYSE_EARLY_HOLIDAYS = [ - datetime.datetime(2023, 7, 3, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 11, 24, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 7, 3, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 11, 29, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 12, 24, tzinfo=NY_TZ).date(), -] - -FX_OPEN_CLOSE_TIME = datetime.time(17, 0, 0, tzinfo=NY_TZ) - -# FX_METAL_HOLIDAYS will need to be updated each year -# From https://www.cboe.com/about/hours/fx/ -FX_HOLIDAYS = [ - datetime.datetime(2023, 1, 1, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 12, 25, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 1, 1, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 12, 25, tzinfo=NY_TZ).date(), -] - -METAL_OPEN_CLOSE_TIME = datetime.time(17, 0, 0, tzinfo=NY_TZ) - - -# References: -# https://www.forex.com/en-ca/help-and-support/market-trading-hours/ -METAL_EARLY_CLOSE = datetime.time(14, 30, 0, tzinfo=NY_TZ) - -# References: -# https://www.ig.com/uk/help-and-support/spread-betting-and-cfds/market-details/martin-luther-king-jr-trading-hours -# https://www.etoro.com/trading/market-hours-and-events/ -METAL_EARLY_CLOSE_OPEN = datetime.time(18, 0, 0, tzinfo=NY_TZ) - -# FX_METAL_HOLIDAYS will need to be updated each year -# From https://www.cboe.com/about/hours/fx/ -METAL_HOLIDAYS = [ - datetime.datetime(2023, 1, 1, tzinfo=NY_TZ).date(), - datetime.datetime(2023, 12, 25, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 1, 1, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 12, 25, tzinfo=NY_TZ).date(), -] -METAL_EARLY_HOLIDAYS = [ - datetime.datetime(2024, 1, 15, tzinfo=NY_TZ).date(), - datetime.datetime(2024, 2, 19, tzinfo=NY_TZ).date(), -] - -RATES_OPEN = datetime.time(8, 0, 0, tzinfo=NY_TZ) -RATES_CLOSE = datetime.time(17, 0, 0, tzinfo=NY_TZ) - - -def is_market_open(asset_type: str, dt: datetime.datetime) -> bool: - # make sure time is in NY timezone - dt = dt.astimezone(NY_TZ) - day, date, time = dt.weekday(), dt.date(), dt.time() - - if asset_type == "equity": - if date in NYSE_HOLIDAYS or date in NYSE_EARLY_HOLIDAYS: - if ( - date in NYSE_EARLY_HOLIDAYS - and time >= EQUITY_OPEN - and time < NYSE_EARLY_CLOSE - ): - return True - return False - if day < 5 and time >= EQUITY_OPEN and time < EQUITY_CLOSE: - return True - return False - - if asset_type == "fx": - if date in FX_HOLIDAYS and time < FX_OPEN_CLOSE_TIME: - return False - # If the next day is a holiday, the market is closed at 5pm ET - if ( - date + datetime.timedelta(days=1) in FX_HOLIDAYS - ) and time >= FX_OPEN_CLOSE_TIME: - return False - # On Friday the market is closed after 5pm - if day == 4 and time >= FX_OPEN_CLOSE_TIME: - return False - # On Saturday the market is closed all the time - if day == 5: - return False - # On Sunday the market is closed before 5pm - if day == 6 and time < FX_OPEN_CLOSE_TIME: - return False - return True - - if asset_type == "metal": - if date in METAL_HOLIDAYS and time < METAL_OPEN_CLOSE_TIME: - return False - # If the next day is a holiday, the market is closed at 5pm ET - if ( - date + datetime.timedelta(days=1) in METAL_HOLIDAYS - ) and time >= METAL_OPEN_CLOSE_TIME: - return False - if ( - date in METAL_EARLY_HOLIDAYS - and time >= METAL_EARLY_CLOSE - and time < METAL_EARLY_CLOSE_OPEN - ): - return False - # On Friday the market is closed after 5pm - if day == 4 and time >= METAL_OPEN_CLOSE_TIME: - return False - # On Saturday the market is closed all the time - if day == 5: - return False - # On Sunday the market is closed before 5pm - if day == 6 and time < METAL_OPEN_CLOSE_TIME: - return False - return True - - if asset_type == "rates": - if date in NYSE_HOLIDAYS or date in NYSE_EARLY_HOLIDAYS: - if ( - date in NYSE_EARLY_HOLIDAYS - and time >= RATES_OPEN - and time < NYSE_EARLY_CLOSE - ): - return True - return False - if day < 5 and time >= RATES_OPEN and time < RATES_CLOSE: - return True - return False - - # all other markets (crypto) - return True - - -def get_next_market_open(asset_type: str, dt: datetime.datetime) -> int: - # make sure time is in NY timezone - dt = dt.astimezone(NY_TZ) - time = dt.time() - - if asset_type == "equity": - if time < EQUITY_OPEN: - next_market_open = dt.replace( - hour=EQUITY_OPEN.hour, - minute=EQUITY_OPEN.minute, - second=0, - microsecond=0, - ) - else: - next_market_open = dt.replace( - hour=EQUITY_OPEN.hour, - minute=EQUITY_OPEN.minute, - second=0, - microsecond=0, - ) - next_market_open += datetime.timedelta(days=1) - elif asset_type == "fx": - if (dt.weekday() == 6 and time < FX_OPEN_CLOSE_TIME) or ( - dt.date() in FX_HOLIDAYS and time < FX_OPEN_CLOSE_TIME - ): - next_market_open = dt.replace( - hour=FX_OPEN_CLOSE_TIME.hour, - minute=FX_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - else: - next_market_open = dt.replace( - hour=FX_OPEN_CLOSE_TIME.hour, - minute=FX_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - while is_market_open(asset_type, next_market_open): - next_market_open += datetime.timedelta(days=1) - elif asset_type == "metal": - if dt.date() in METAL_EARLY_HOLIDAYS and time < METAL_EARLY_CLOSE_OPEN: - next_market_open = dt.replace( - hour=METAL_EARLY_CLOSE_OPEN.hour, - minute=METAL_EARLY_CLOSE_OPEN.minute, - second=0, - microsecond=0, - ) - elif dt.date() in METAL_EARLY_HOLIDAYS and time >= METAL_EARLY_CLOSE_OPEN: - next_market_open = dt.replace( - hour=METAL_OPEN_CLOSE_TIME.hour, - minute=METAL_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - next_market_open += datetime.timedelta(days=1) - while is_market_open(asset_type, next_market_open): - next_market_open += datetime.timedelta(days=1) - else: - if (dt.weekday() == 6 and time < METAL_OPEN_CLOSE_TIME) or ( - dt.date() in METAL_HOLIDAYS and time < METAL_OPEN_CLOSE_TIME - ): - next_market_open = dt.replace( - hour=METAL_OPEN_CLOSE_TIME.hour, - minute=METAL_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - else: - next_market_open = dt.replace( - hour=METAL_OPEN_CLOSE_TIME.hour, - minute=METAL_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - while is_market_open(asset_type, next_market_open): - next_market_open += datetime.timedelta(days=1) - elif asset_type == "rates": - if time < RATES_OPEN: - next_market_open = dt.replace( - hour=RATES_OPEN.hour, - minute=RATES_OPEN.minute, - second=0, - microsecond=0, - ) - else: - next_market_open = dt.replace( - hour=RATES_OPEN.hour, - minute=RATES_OPEN.minute, - second=0, - microsecond=0, - ) - next_market_open += datetime.timedelta(days=1) - else: - return None - - while not is_market_open(asset_type, next_market_open): - next_market_open += datetime.timedelta(days=1) - - return int(next_market_open.timestamp()) - - -def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int: - # make sure time is in NY timezone - dt = dt.astimezone(NY_TZ) - time = dt.time() - - if asset_type == "equity": - if dt.date() in NYSE_EARLY_HOLIDAYS: - if time < NYSE_EARLY_CLOSE: - next_market_close = dt.replace( - hour=NYSE_EARLY_CLOSE.hour, - minute=NYSE_EARLY_CLOSE.minute, - second=0, - microsecond=0, - ) - else: - next_market_close = dt.replace( - hour=EQUITY_CLOSE.hour, - minute=EQUITY_CLOSE.minute, - second=0, - microsecond=0, - ) - next_market_close += datetime.timedelta(days=1) - elif dt.date() in NYSE_HOLIDAYS: - next_market_open = get_next_market_open(asset_type, dt) - next_market_open_date = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .date() - ) - if next_market_open_date in NYSE_EARLY_HOLIDAYS: - next_market_close = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .replace( - hour=NYSE_EARLY_CLOSE.hour, - minute=NYSE_EARLY_CLOSE.minute, - second=0, - microsecond=0, - ) - ) - else: - next_market_close = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .replace( - hour=EQUITY_CLOSE.hour, - minute=EQUITY_CLOSE.minute, - second=0, - microsecond=0, - ) - ) - else: - next_market_close = dt.replace( - hour=EQUITY_CLOSE.hour, - minute=EQUITY_CLOSE.minute, - second=0, - microsecond=0, - ) - if time >= EQUITY_CLOSE: - next_market_close += datetime.timedelta(days=1) - - # while next_market_close.date() is in NYSE_HOLIDAYS or weekend, add 1 day - while ( - next_market_close.date() in NYSE_HOLIDAYS - or next_market_close.weekday() >= 5 - ): - next_market_close += datetime.timedelta(days=1) - - elif asset_type == "fx": - next_market_close = dt.replace( - hour=FX_OPEN_CLOSE_TIME.hour, - minute=FX_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - if dt.weekday() != 4: - while not is_market_open(asset_type, next_market_close): - next_market_close += datetime.timedelta(days=1) - while is_market_open(asset_type, next_market_close): - next_market_close += datetime.timedelta(days=1) - elif asset_type == "metal": - if dt.date() in METAL_EARLY_HOLIDAYS and time < METAL_EARLY_CLOSE: - next_market_close = dt.replace( - hour=METAL_EARLY_CLOSE.hour, - minute=METAL_EARLY_CLOSE.minute, - second=0, - microsecond=0, - ) - elif dt.date() in METAL_EARLY_HOLIDAYS and time >= METAL_EARLY_CLOSE: - next_market_close = dt.replace( - hour=METAL_OPEN_CLOSE_TIME.hour, - minute=METAL_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - next_market_close += datetime.timedelta(days=1) - while is_market_open(asset_type, next_market_close): - next_market_close += datetime.timedelta(days=1) - else: - next_market_close = dt.replace( - hour=METAL_OPEN_CLOSE_TIME.hour, - minute=METAL_OPEN_CLOSE_TIME.minute, - second=0, - microsecond=0, - ) - if dt.weekday() != 4: - while not is_market_open(asset_type, next_market_close): - next_market_close += datetime.timedelta(days=1) - while is_market_open(asset_type, next_market_close): - next_market_close += datetime.timedelta(days=1) - elif asset_type == "rates": - if dt.date() in NYSE_EARLY_HOLIDAYS: - if time < NYSE_EARLY_CLOSE: - next_market_close = dt.replace( - hour=NYSE_EARLY_CLOSE.hour, - minute=NYSE_EARLY_CLOSE.minute, - second=0, - microsecond=0, - ) - else: - next_market_close = dt.replace( - hour=RATES_CLOSE.hour, - minute=RATES_CLOSE.minute, - second=0, - microsecond=0, - ) - next_market_close += datetime.timedelta(days=1) - elif dt.date() in NYSE_HOLIDAYS: - next_market_open = get_next_market_open(asset_type, dt) - next_market_open_date = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .date() - ) - if next_market_open_date in NYSE_EARLY_HOLIDAYS: - next_market_close = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .replace( - hour=NYSE_EARLY_CLOSE.hour, - minute=NYSE_EARLY_CLOSE.minute, - second=0, - microsecond=0, - ) - ) - else: - next_market_close = ( - datetime.datetime.fromtimestamp(next_market_open) - .astimezone(NY_TZ) - .replace( - hour=RATES_CLOSE.hour, - minute=RATES_CLOSE.minute, - second=0, - microsecond=0, - ) - ) - else: - next_market_close = dt.replace( - hour=RATES_CLOSE.hour, - minute=RATES_CLOSE.minute, - second=0, - microsecond=0, - ) - if time >= RATES_CLOSE: - next_market_close += datetime.timedelta(days=1) - - # while next_market_close.date() is in NYSE_HOLIDAYS or weekend, add 1 day - while ( - next_market_close.date() in NYSE_HOLIDAYS - or next_market_close.weekday() >= 5 - ): - next_market_close += datetime.timedelta(days=1) - else: # crypto markets never close - return None - - return int(next_market_close.timestamp()) diff --git a/pythclient/calendar_full_intervals.py b/pythclient/calendar_full_intervals.py deleted file mode 100644 index 0ed8aba..0000000 --- a/pythclient/calendar_full_intervals.py +++ /dev/null @@ -1,1481 +0,0 @@ -import datetime - -EQUITY_2024_INTERVALS = [ - (datetime.date(2024, 1, 1), None), - (datetime.date(2024, 1, 2), "0930-1600"), - (datetime.date(2024, 1, 3), "0930-1600"), - (datetime.date(2024, 1, 4), "0930-1600"), - (datetime.date(2024, 1, 5), "0930-1600"), - (datetime.date(2024, 1, 6), None), - (datetime.date(2024, 1, 7), None), - (datetime.date(2024, 1, 8), "0930-1600"), - (datetime.date(2024, 1, 9), "0930-1600"), - (datetime.date(2024, 1, 10), "0930-1600"), - (datetime.date(2024, 1, 11), "0930-1600"), - (datetime.date(2024, 1, 12), "0930-1600"), - (datetime.date(2024, 1, 13), None), - (datetime.date(2024, 1, 14), None), - (datetime.date(2024, 1, 15), None), - (datetime.date(2024, 1, 16), "0930-1600"), - (datetime.date(2024, 1, 17), "0930-1600"), - (datetime.date(2024, 1, 18), "0930-1600"), - (datetime.date(2024, 1, 19), "0930-1600"), - (datetime.date(2024, 1, 20), None), - (datetime.date(2024, 1, 21), None), - (datetime.date(2024, 1, 22), "0930-1600"), - (datetime.date(2024, 1, 23), "0930-1600"), - (datetime.date(2024, 1, 24), "0930-1600"), - (datetime.date(2024, 1, 25), "0930-1600"), - (datetime.date(2024, 1, 26), "0930-1600"), - (datetime.date(2024, 1, 27), None), - (datetime.date(2024, 1, 28), None), - (datetime.date(2024, 1, 29), "0930-1600"), - (datetime.date(2024, 1, 30), "0930-1600"), - (datetime.date(2024, 1, 31), "0930-1600"), - (datetime.date(2024, 2, 1), "0930-1600"), - (datetime.date(2024, 2, 2), "0930-1600"), - (datetime.date(2024, 2, 3), None), - (datetime.date(2024, 2, 4), None), - (datetime.date(2024, 2, 5), "0930-1600"), - (datetime.date(2024, 2, 6), "0930-1600"), - (datetime.date(2024, 2, 7), "0930-1600"), - (datetime.date(2024, 2, 8), "0930-1600"), - (datetime.date(2024, 2, 9), "0930-1600"), - (datetime.date(2024, 2, 10), None), - (datetime.date(2024, 2, 11), None), - (datetime.date(2024, 2, 12), "0930-1600"), - (datetime.date(2024, 2, 13), "0930-1600"), - (datetime.date(2024, 2, 14), "0930-1600"), - (datetime.date(2024, 2, 15), "0930-1600"), - (datetime.date(2024, 2, 16), "0930-1600"), - (datetime.date(2024, 2, 17), None), - (datetime.date(2024, 2, 18), None), - (datetime.date(2024, 2, 19), None), - (datetime.date(2024, 2, 20), "0930-1600"), - (datetime.date(2024, 2, 21), "0930-1600"), - (datetime.date(2024, 2, 22), "0930-1600"), - (datetime.date(2024, 2, 23), "0930-1600"), - (datetime.date(2024, 2, 24), None), - (datetime.date(2024, 2, 25), None), - (datetime.date(2024, 2, 26), "0930-1600"), - (datetime.date(2024, 2, 27), "0930-1600"), - (datetime.date(2024, 2, 28), "0930-1600"), - (datetime.date(2024, 2, 29), "0930-1600"), - (datetime.date(2024, 3, 1), "0930-1600"), - (datetime.date(2024, 3, 2), None), - (datetime.date(2024, 3, 3), None), - (datetime.date(2024, 3, 4), "0930-1600"), - (datetime.date(2024, 3, 5), "0930-1600"), - (datetime.date(2024, 3, 6), "0930-1600"), - (datetime.date(2024, 3, 7), "0930-1600"), - (datetime.date(2024, 3, 8), "0930-1600"), - (datetime.date(2024, 3, 9), None), - (datetime.date(2024, 3, 10), None), - (datetime.date(2024, 3, 11), "0930-1600"), - (datetime.date(2024, 3, 12), "0930-1600"), - (datetime.date(2024, 3, 13), "0930-1600"), - (datetime.date(2024, 3, 14), "0930-1600"), - (datetime.date(2024, 3, 15), "0930-1600"), - (datetime.date(2024, 3, 16), None), - (datetime.date(2024, 3, 17), None), - (datetime.date(2024, 3, 18), "0930-1600"), - (datetime.date(2024, 3, 19), "0930-1600"), - (datetime.date(2024, 3, 20), "0930-1600"), - (datetime.date(2024, 3, 21), "0930-1600"), - (datetime.date(2024, 3, 22), "0930-1600"), - (datetime.date(2024, 3, 23), None), - (datetime.date(2024, 3, 24), None), - (datetime.date(2024, 3, 25), "0930-1600"), - (datetime.date(2024, 3, 26), "0930-1600"), - (datetime.date(2024, 3, 27), "0930-1600"), - (datetime.date(2024, 3, 28), "0930-1600"), - (datetime.date(2024, 3, 29), None), - (datetime.date(2024, 3, 30), None), - (datetime.date(2024, 3, 31), None), - (datetime.date(2024, 4, 1), "0930-1600"), - (datetime.date(2024, 4, 2), "0930-1600"), - (datetime.date(2024, 4, 3), "0930-1600"), - (datetime.date(2024, 4, 4), "0930-1600"), - (datetime.date(2024, 4, 5), "0930-1600"), - (datetime.date(2024, 4, 6), None), - (datetime.date(2024, 4, 7), None), - (datetime.date(2024, 4, 8), "0930-1600"), - (datetime.date(2024, 4, 9), "0930-1600"), - (datetime.date(2024, 4, 10), "0930-1600"), - (datetime.date(2024, 4, 11), "0930-1600"), - (datetime.date(2024, 4, 12), "0930-1600"), - (datetime.date(2024, 4, 13), None), - (datetime.date(2024, 4, 14), None), - (datetime.date(2024, 4, 15), "0930-1600"), - (datetime.date(2024, 4, 16), "0930-1600"), - (datetime.date(2024, 4, 17), "0930-1600"), - (datetime.date(2024, 4, 18), "0930-1600"), - (datetime.date(2024, 4, 19), "0930-1600"), - (datetime.date(2024, 4, 20), None), - (datetime.date(2024, 4, 21), None), - (datetime.date(2024, 4, 22), "0930-1600"), - (datetime.date(2024, 4, 23), "0930-1600"), - (datetime.date(2024, 4, 24), "0930-1600"), - (datetime.date(2024, 4, 25), "0930-1600"), - (datetime.date(2024, 4, 26), "0930-1600"), - (datetime.date(2024, 4, 27), None), - (datetime.date(2024, 4, 28), None), - (datetime.date(2024, 4, 29), "0930-1600"), - (datetime.date(2024, 4, 30), "0930-1600"), - (datetime.date(2024, 5, 1), "0930-1600"), - (datetime.date(2024, 5, 2), "0930-1600"), - (datetime.date(2024, 5, 3), "0930-1600"), - (datetime.date(2024, 5, 4), None), - (datetime.date(2024, 5, 5), None), - (datetime.date(2024, 5, 6), "0930-1600"), - (datetime.date(2024, 5, 7), "0930-1600"), - (datetime.date(2024, 5, 8), "0930-1600"), - (datetime.date(2024, 5, 9), "0930-1600"), - (datetime.date(2024, 5, 10), "0930-1600"), - (datetime.date(2024, 5, 11), None), - (datetime.date(2024, 5, 12), None), - (datetime.date(2024, 5, 13), "0930-1600"), - (datetime.date(2024, 5, 14), "0930-1600"), - (datetime.date(2024, 5, 15), "0930-1600"), - (datetime.date(2024, 5, 16), "0930-1600"), - (datetime.date(2024, 5, 17), "0930-1600"), - (datetime.date(2024, 5, 18), None), - (datetime.date(2024, 5, 19), None), - (datetime.date(2024, 5, 20), "0930-1600"), - (datetime.date(2024, 5, 21), "0930-1600"), - (datetime.date(2024, 5, 22), "0930-1600"), - (datetime.date(2024, 5, 23), "0930-1600"), - (datetime.date(2024, 5, 24), "0930-1600"), - (datetime.date(2024, 5, 25), None), - (datetime.date(2024, 5, 26), None), - (datetime.date(2024, 5, 27), None), - (datetime.date(2024, 5, 28), "0930-1600"), - (datetime.date(2024, 5, 29), "0930-1600"), - (datetime.date(2024, 5, 30), "0930-1600"), - (datetime.date(2024, 5, 31), "0930-1600"), - (datetime.date(2024, 6, 1), None), - (datetime.date(2024, 6, 2), None), - (datetime.date(2024, 6, 3), "0930-1600"), - (datetime.date(2024, 6, 4), "0930-1600"), - (datetime.date(2024, 6, 5), "0930-1600"), - (datetime.date(2024, 6, 6), "0930-1600"), - (datetime.date(2024, 6, 7), "0930-1600"), - (datetime.date(2024, 6, 8), None), - (datetime.date(2024, 6, 9), None), - (datetime.date(2024, 6, 10), "0930-1600"), - (datetime.date(2024, 6, 11), "0930-1600"), - (datetime.date(2024, 6, 12), "0930-1600"), - (datetime.date(2024, 6, 13), "0930-1600"), - (datetime.date(2024, 6, 14), "0930-1600"), - (datetime.date(2024, 6, 15), None), - (datetime.date(2024, 6, 16), None), - (datetime.date(2024, 6, 17), "0930-1600"), - (datetime.date(2024, 6, 18), "0930-1600"), - (datetime.date(2024, 6, 19), None), - (datetime.date(2024, 6, 20), "0930-1600"), - (datetime.date(2024, 6, 21), "0930-1600"), - (datetime.date(2024, 6, 22), None), - (datetime.date(2024, 6, 23), None), - (datetime.date(2024, 6, 24), "0930-1600"), - (datetime.date(2024, 6, 25), "0930-1600"), - (datetime.date(2024, 6, 26), "0930-1600"), - (datetime.date(2024, 6, 27), "0930-1600"), - (datetime.date(2024, 6, 28), "0930-1600"), - (datetime.date(2024, 6, 29), None), - (datetime.date(2024, 6, 30), None), - (datetime.date(2024, 7, 1), "0930-1600"), - (datetime.date(2024, 7, 2), "0930-1600"), - (datetime.date(2024, 7, 3), "0930-1300"), - (datetime.date(2024, 7, 4), None), - (datetime.date(2024, 7, 5), "0930-1600"), - (datetime.date(2024, 7, 6), None), - (datetime.date(2024, 7, 7), None), - (datetime.date(2024, 7, 8), "0930-1600"), - (datetime.date(2024, 7, 9), "0930-1600"), - (datetime.date(2024, 7, 10), "0930-1600"), - (datetime.date(2024, 7, 11), "0930-1600"), - (datetime.date(2024, 7, 12), "0930-1600"), - (datetime.date(2024, 7, 13), None), - (datetime.date(2024, 7, 14), None), - (datetime.date(2024, 7, 15), "0930-1600"), - (datetime.date(2024, 7, 16), "0930-1600"), - (datetime.date(2024, 7, 17), "0930-1600"), - (datetime.date(2024, 7, 18), "0930-1600"), - (datetime.date(2024, 7, 19), "0930-1600"), - (datetime.date(2024, 7, 20), None), - (datetime.date(2024, 7, 21), None), - (datetime.date(2024, 7, 22), "0930-1600"), - (datetime.date(2024, 7, 23), "0930-1600"), - (datetime.date(2024, 7, 24), "0930-1600"), - (datetime.date(2024, 7, 25), "0930-1600"), - (datetime.date(2024, 7, 26), "0930-1600"), - (datetime.date(2024, 7, 27), None), - (datetime.date(2024, 7, 28), None), - (datetime.date(2024, 7, 29), "0930-1600"), - (datetime.date(2024, 7, 30), "0930-1600"), - (datetime.date(2024, 7, 31), "0930-1600"), - (datetime.date(2024, 8, 1), "0930-1600"), - (datetime.date(2024, 8, 2), "0930-1600"), - (datetime.date(2024, 8, 3), None), - (datetime.date(2024, 8, 4), None), - (datetime.date(2024, 8, 5), "0930-1600"), - (datetime.date(2024, 8, 6), "0930-1600"), - (datetime.date(2024, 8, 7), "0930-1600"), - (datetime.date(2024, 8, 8), "0930-1600"), - (datetime.date(2024, 8, 9), "0930-1600"), - (datetime.date(2024, 8, 10), None), - (datetime.date(2024, 8, 11), None), - (datetime.date(2024, 8, 12), "0930-1600"), - (datetime.date(2024, 8, 13), "0930-1600"), - (datetime.date(2024, 8, 14), "0930-1600"), - (datetime.date(2024, 8, 15), "0930-1600"), - (datetime.date(2024, 8, 16), "0930-1600"), - (datetime.date(2024, 8, 17), None), - (datetime.date(2024, 8, 18), None), - (datetime.date(2024, 8, 19), "0930-1600"), - (datetime.date(2024, 8, 20), "0930-1600"), - (datetime.date(2024, 8, 21), "0930-1600"), - (datetime.date(2024, 8, 22), "0930-1600"), - (datetime.date(2024, 8, 23), "0930-1600"), - (datetime.date(2024, 8, 24), None), - (datetime.date(2024, 8, 25), None), - (datetime.date(2024, 8, 26), "0930-1600"), - (datetime.date(2024, 8, 27), "0930-1600"), - (datetime.date(2024, 8, 28), "0930-1600"), - (datetime.date(2024, 8, 29), "0930-1600"), - (datetime.date(2024, 8, 30), "0930-1600"), - (datetime.date(2024, 8, 31), None), - (datetime.date(2024, 9, 1), None), - (datetime.date(2024, 9, 2), None), - (datetime.date(2024, 9, 3), "0930-1600"), - (datetime.date(2024, 9, 4), "0930-1600"), - (datetime.date(2024, 9, 5), "0930-1600"), - (datetime.date(2024, 9, 6), "0930-1600"), - (datetime.date(2024, 9, 7), None), - (datetime.date(2024, 9, 8), None), - (datetime.date(2024, 9, 9), "0930-1600"), - (datetime.date(2024, 9, 10), "0930-1600"), - (datetime.date(2024, 9, 11), "0930-1600"), - (datetime.date(2024, 9, 12), "0930-1600"), - (datetime.date(2024, 9, 13), "0930-1600"), - (datetime.date(2024, 9, 14), None), - (datetime.date(2024, 9, 15), None), - (datetime.date(2024, 9, 16), "0930-1600"), - (datetime.date(2024, 9, 17), "0930-1600"), - (datetime.date(2024, 9, 18), "0930-1600"), - (datetime.date(2024, 9, 19), "0930-1600"), - (datetime.date(2024, 9, 20), "0930-1600"), - (datetime.date(2024, 9, 21), None), - (datetime.date(2024, 9, 22), None), - (datetime.date(2024, 9, 23), "0930-1600"), - (datetime.date(2024, 9, 24), "0930-1600"), - (datetime.date(2024, 9, 25), "0930-1600"), - (datetime.date(2024, 9, 26), "0930-1600"), - (datetime.date(2024, 9, 27), "0930-1600"), - (datetime.date(2024, 9, 28), None), - (datetime.date(2024, 9, 29), None), - (datetime.date(2024, 9, 30), "0930-1600"), - (datetime.date(2024, 10, 1), "0930-1600"), - (datetime.date(2024, 10, 2), "0930-1600"), - (datetime.date(2024, 10, 3), "0930-1600"), - (datetime.date(2024, 10, 4), "0930-1600"), - (datetime.date(2024, 10, 5), None), - (datetime.date(2024, 10, 6), None), - (datetime.date(2024, 10, 7), "0930-1600"), - (datetime.date(2024, 10, 8), "0930-1600"), - (datetime.date(2024, 10, 9), "0930-1600"), - (datetime.date(2024, 10, 10), "0930-1600"), - (datetime.date(2024, 10, 11), "0930-1600"), - (datetime.date(2024, 10, 12), None), - (datetime.date(2024, 10, 13), None), - (datetime.date(2024, 10, 14), "0930-1600"), - (datetime.date(2024, 10, 15), "0930-1600"), - (datetime.date(2024, 10, 16), "0930-1600"), - (datetime.date(2024, 10, 17), "0930-1600"), - (datetime.date(2024, 10, 18), "0930-1600"), - (datetime.date(2024, 10, 19), None), - (datetime.date(2024, 10, 20), None), - (datetime.date(2024, 10, 21), "0930-1600"), - (datetime.date(2024, 10, 22), "0930-1600"), - (datetime.date(2024, 10, 23), "0930-1600"), - (datetime.date(2024, 10, 24), "0930-1600"), - (datetime.date(2024, 10, 25), "0930-1600"), - (datetime.date(2024, 10, 26), None), - (datetime.date(2024, 10, 27), None), - (datetime.date(2024, 10, 28), "0930-1600"), - (datetime.date(2024, 10, 29), "0930-1600"), - (datetime.date(2024, 10, 30), "0930-1600"), - (datetime.date(2024, 10, 31), "0930-1600"), - (datetime.date(2024, 11, 1), "0930-1600"), - (datetime.date(2024, 11, 2), None), - (datetime.date(2024, 11, 3), None), - (datetime.date(2024, 11, 4), "0930-1600"), - (datetime.date(2024, 11, 5), "0930-1600"), - (datetime.date(2024, 11, 6), "0930-1600"), - (datetime.date(2024, 11, 7), "0930-1600"), - (datetime.date(2024, 11, 8), "0930-1600"), - (datetime.date(2024, 11, 9), None), - (datetime.date(2024, 11, 10), None), - (datetime.date(2024, 11, 11), "0930-1600"), - (datetime.date(2024, 11, 12), "0930-1600"), - (datetime.date(2024, 11, 13), "0930-1600"), - (datetime.date(2024, 11, 14), "0930-1600"), - (datetime.date(2024, 11, 15), "0930-1600"), - (datetime.date(2024, 11, 16), None), - (datetime.date(2024, 11, 17), None), - (datetime.date(2024, 11, 18), "0930-1600"), - (datetime.date(2024, 11, 19), "0930-1600"), - (datetime.date(2024, 11, 20), "0930-1600"), - (datetime.date(2024, 11, 21), "0930-1600"), - (datetime.date(2024, 11, 22), "0930-1600"), - (datetime.date(2024, 11, 23), None), - (datetime.date(2024, 11, 24), None), - (datetime.date(2024, 11, 25), "0930-1600"), - (datetime.date(2024, 11, 26), "0930-1600"), - (datetime.date(2024, 11, 27), "0930-1600"), - (datetime.date(2024, 11, 28), None), - (datetime.date(2024, 11, 29), "0930-1300"), - (datetime.date(2024, 11, 30), None), - (datetime.date(2024, 12, 1), None), - (datetime.date(2024, 12, 2), "0930-1600"), - (datetime.date(2024, 12, 3), "0930-1600"), - (datetime.date(2024, 12, 4), "0930-1600"), - (datetime.date(2024, 12, 5), "0930-1600"), - (datetime.date(2024, 12, 6), "0930-1600"), - (datetime.date(2024, 12, 7), None), - (datetime.date(2024, 12, 8), None), - (datetime.date(2024, 12, 9), "0930-1600"), - (datetime.date(2024, 12, 10), "0930-1600"), - (datetime.date(2024, 12, 11), "0930-1600"), - (datetime.date(2024, 12, 12), "0930-1600"), - (datetime.date(2024, 12, 13), "0930-1600"), - (datetime.date(2024, 12, 14), None), - (datetime.date(2024, 12, 15), None), - (datetime.date(2024, 12, 16), "0930-1600"), - (datetime.date(2024, 12, 17), "0930-1600"), - (datetime.date(2024, 12, 18), "0930-1600"), - (datetime.date(2024, 12, 19), "0930-1600"), - (datetime.date(2024, 12, 20), "0930-1600"), - (datetime.date(2024, 12, 21), None), - (datetime.date(2024, 12, 22), None), - (datetime.date(2024, 12, 23), "0930-1600"), - (datetime.date(2024, 12, 24), "0930-1300"), - (datetime.date(2024, 12, 25), None), - (datetime.date(2024, 12, 26), "0930-1600"), - (datetime.date(2024, 12, 27), "0930-1600"), - (datetime.date(2024, 12, 28), None), - (datetime.date(2024, 12, 29), None), - (datetime.date(2024, 12, 30), "0930-1600"), - (datetime.date(2024, 12, 31), "0930-1600"), -] - -FX_2024_INTERVALS = [ - (datetime.date(2023, 12, 31), None), - (datetime.date(2024, 1, 1), "1700-0000"), - (datetime.date(2024, 1, 2), "0000-0000"), - (datetime.date(2024, 1, 3), "0000-0000"), - (datetime.date(2024, 1, 4), "0000-0000"), - (datetime.date(2024, 1, 5), "0000-1700"), - (datetime.date(2024, 1, 6), None), - (datetime.date(2024, 1, 7), "1700-0000"), - (datetime.date(2024, 1, 8), "0000-0000"), - (datetime.date(2024, 1, 9), "0000-0000"), - (datetime.date(2024, 1, 10), "0000-0000"), - (datetime.date(2024, 1, 11), "0000-0000"), - (datetime.date(2024, 1, 12), "0000-1700"), - (datetime.date(2024, 1, 13), None), - (datetime.date(2024, 1, 14), "1700-0000"), - (datetime.date(2024, 1, 15), "0000-0000"), - (datetime.date(2024, 1, 16), "0000-0000"), - (datetime.date(2024, 1, 17), "0000-0000"), - (datetime.date(2024, 1, 18), "0000-0000"), - (datetime.date(2024, 1, 19), "0000-1700"), - (datetime.date(2024, 1, 20), None), - (datetime.date(2024, 1, 21), "1700-0000"), - (datetime.date(2024, 1, 22), "0000-0000"), - (datetime.date(2024, 1, 23), "0000-0000"), - (datetime.date(2024, 1, 24), "0000-0000"), - (datetime.date(2024, 1, 25), "0000-0000"), - (datetime.date(2024, 1, 26), "0000-1700"), - (datetime.date(2024, 1, 27), None), - (datetime.date(2024, 1, 28), "1700-0000"), - (datetime.date(2024, 1, 29), "0000-0000"), - (datetime.date(2024, 1, 30), "0000-0000"), - (datetime.date(2024, 1, 31), "0000-0000"), - (datetime.date(2024, 2, 1), "0000-0000"), - (datetime.date(2024, 2, 2), "0000-1700"), - (datetime.date(2024, 2, 3), None), - (datetime.date(2024, 2, 4), "1700-0000"), - (datetime.date(2024, 2, 5), "0000-0000"), - (datetime.date(2024, 2, 6), "0000-0000"), - (datetime.date(2024, 2, 7), "0000-0000"), - (datetime.date(2024, 2, 8), "0000-0000"), - (datetime.date(2024, 2, 9), "0000-1700"), - (datetime.date(2024, 2, 10), None), - (datetime.date(2024, 2, 11), "1700-0000"), - (datetime.date(2024, 2, 12), "0000-0000"), - (datetime.date(2024, 2, 13), "0000-0000"), - (datetime.date(2024, 2, 14), "0000-0000"), - (datetime.date(2024, 2, 15), "0000-0000"), - (datetime.date(2024, 2, 16), "0000-1700"), - (datetime.date(2024, 2, 17), None), - (datetime.date(2024, 2, 18), "1700-0000"), - (datetime.date(2024, 2, 19), "0000-0000"), - (datetime.date(2024, 2, 20), "0000-0000"), - (datetime.date(2024, 2, 21), "0000-0000"), - (datetime.date(2024, 2, 22), "0000-0000"), - (datetime.date(2024, 2, 23), "0000-1700"), - (datetime.date(2024, 2, 24), None), - (datetime.date(2024, 2, 25), "1700-0000"), - (datetime.date(2024, 2, 26), "0000-0000"), - (datetime.date(2024, 2, 27), "0000-0000"), - (datetime.date(2024, 2, 28), "0000-0000"), - (datetime.date(2024, 2, 29), "0000-0000"), - (datetime.date(2024, 3, 1), "0000-1700"), - (datetime.date(2024, 3, 2), None), - (datetime.date(2024, 3, 3), "1700-0000"), - (datetime.date(2024, 3, 4), "0000-0000"), - (datetime.date(2024, 3, 5), "0000-0000"), - (datetime.date(2024, 3, 6), "0000-0000"), - (datetime.date(2024, 3, 7), "0000-0000"), - (datetime.date(2024, 3, 8), "0000-1700"), - (datetime.date(2024, 3, 9), None), - (datetime.date(2024, 3, 10), "1700-0000"), - (datetime.date(2024, 3, 11), "0000-0000"), - (datetime.date(2024, 3, 12), "0000-0000"), - (datetime.date(2024, 3, 13), "0000-0000"), - (datetime.date(2024, 3, 14), "0000-0000"), - (datetime.date(2024, 3, 15), "0000-1700"), - (datetime.date(2024, 3, 16), None), - (datetime.date(2024, 3, 17), "1700-0000"), - (datetime.date(2024, 3, 18), "0000-0000"), - (datetime.date(2024, 3, 19), "0000-0000"), - (datetime.date(2024, 3, 20), "0000-0000"), - (datetime.date(2024, 3, 21), "0000-0000"), - (datetime.date(2024, 3, 22), "0000-1700"), - (datetime.date(2024, 3, 23), None), - (datetime.date(2024, 3, 24), "1700-0000"), - (datetime.date(2024, 3, 25), "0000-0000"), - (datetime.date(2024, 3, 26), "0000-0000"), - (datetime.date(2024, 3, 27), "0000-0000"), - (datetime.date(2024, 3, 28), "0000-0000"), - (datetime.date(2024, 3, 29), "0000-1700"), - (datetime.date(2024, 3, 30), None), - (datetime.date(2024, 3, 31), "1700-0000"), - (datetime.date(2024, 4, 1), "0000-0000"), - (datetime.date(2024, 4, 2), "0000-0000"), - (datetime.date(2024, 4, 3), "0000-0000"), - (datetime.date(2024, 4, 4), "0000-0000"), - (datetime.date(2024, 4, 5), "0000-1700"), - (datetime.date(2024, 4, 6), None), - (datetime.date(2024, 4, 7), "1700-0000"), - (datetime.date(2024, 4, 8), "0000-0000"), - (datetime.date(2024, 4, 9), "0000-0000"), - (datetime.date(2024, 4, 10), "0000-0000"), - (datetime.date(2024, 4, 11), "0000-0000"), - (datetime.date(2024, 4, 12), "0000-1700"), - (datetime.date(2024, 4, 13), None), - (datetime.date(2024, 4, 14), "1700-0000"), - (datetime.date(2024, 4, 15), "0000-0000"), - (datetime.date(2024, 4, 16), "0000-0000"), - (datetime.date(2024, 4, 17), "0000-0000"), - (datetime.date(2024, 4, 18), "0000-0000"), - (datetime.date(2024, 4, 19), "0000-1700"), - (datetime.date(2024, 4, 20), None), - (datetime.date(2024, 4, 21), "1700-0000"), - (datetime.date(2024, 4, 22), "0000-0000"), - (datetime.date(2024, 4, 23), "0000-0000"), - (datetime.date(2024, 4, 24), "0000-0000"), - (datetime.date(2024, 4, 25), "0000-0000"), - (datetime.date(2024, 4, 26), "0000-1700"), - (datetime.date(2024, 4, 27), None), - (datetime.date(2024, 4, 28), "1700-0000"), - (datetime.date(2024, 4, 29), "0000-0000"), - (datetime.date(2024, 4, 30), "0000-0000"), - (datetime.date(2024, 5, 1), "0000-0000"), - (datetime.date(2024, 5, 2), "0000-0000"), - (datetime.date(2024, 5, 3), "0000-1700"), - (datetime.date(2024, 5, 4), None), - (datetime.date(2024, 5, 5), "1700-0000"), - (datetime.date(2024, 5, 6), "0000-0000"), - (datetime.date(2024, 5, 7), "0000-0000"), - (datetime.date(2024, 5, 8), "0000-0000"), - (datetime.date(2024, 5, 9), "0000-0000"), - (datetime.date(2024, 5, 10), "0000-1700"), - (datetime.date(2024, 5, 11), None), - (datetime.date(2024, 5, 12), "1700-0000"), - (datetime.date(2024, 5, 13), "0000-0000"), - (datetime.date(2024, 5, 14), "0000-0000"), - (datetime.date(2024, 5, 15), "0000-0000"), - (datetime.date(2024, 5, 16), "0000-0000"), - (datetime.date(2024, 5, 17), "0000-1700"), - (datetime.date(2024, 5, 18), None), - (datetime.date(2024, 5, 19), "1700-0000"), - (datetime.date(2024, 5, 20), "0000-0000"), - (datetime.date(2024, 5, 21), "0000-0000"), - (datetime.date(2024, 5, 22), "0000-0000"), - (datetime.date(2024, 5, 23), "0000-0000"), - (datetime.date(2024, 5, 24), "0000-1700"), - (datetime.date(2024, 5, 25), None), - (datetime.date(2024, 5, 26), "1700-0000"), - (datetime.date(2024, 5, 27), "0000-0000"), - (datetime.date(2024, 5, 28), "0000-0000"), - (datetime.date(2024, 5, 29), "0000-0000"), - (datetime.date(2024, 5, 30), "0000-0000"), - (datetime.date(2024, 5, 31), "0000-1700"), - (datetime.date(2024, 6, 1), None), - (datetime.date(2024, 6, 2), "1700-0000"), - (datetime.date(2024, 6, 3), "0000-0000"), - (datetime.date(2024, 6, 4), "0000-0000"), - (datetime.date(2024, 6, 5), "0000-0000"), - (datetime.date(2024, 6, 6), "0000-0000"), - (datetime.date(2024, 6, 7), "0000-1700"), - (datetime.date(2024, 6, 8), None), - (datetime.date(2024, 6, 9), "1700-0000"), - (datetime.date(2024, 6, 10), "0000-0000"), - (datetime.date(2024, 6, 11), "0000-0000"), - (datetime.date(2024, 6, 12), "0000-0000"), - (datetime.date(2024, 6, 13), "0000-0000"), - (datetime.date(2024, 6, 14), "0000-1700"), - (datetime.date(2024, 6, 15), None), - (datetime.date(2024, 6, 16), "1700-0000"), - (datetime.date(2024, 6, 17), "0000-0000"), - (datetime.date(2024, 6, 18), "0000-0000"), - (datetime.date(2024, 6, 19), "0000-0000"), - (datetime.date(2024, 6, 20), "0000-0000"), - (datetime.date(2024, 6, 21), "0000-1700"), - (datetime.date(2024, 6, 22), None), - (datetime.date(2024, 6, 23), "1700-0000"), - (datetime.date(2024, 6, 24), "0000-0000"), - (datetime.date(2024, 6, 25), "0000-0000"), - (datetime.date(2024, 6, 26), "0000-0000"), - (datetime.date(2024, 6, 27), "0000-0000"), - (datetime.date(2024, 6, 28), "0000-1700"), - (datetime.date(2024, 6, 29), None), - (datetime.date(2024, 6, 30), "1700-0000"), - (datetime.date(2024, 7, 1), "0000-0000"), - (datetime.date(2024, 7, 2), "0000-0000"), - (datetime.date(2024, 7, 3), "0000-0000"), - (datetime.date(2024, 7, 4), "0000-0000"), - (datetime.date(2024, 7, 5), "0000-1700"), - (datetime.date(2024, 7, 6), None), - (datetime.date(2024, 7, 7), "1700-0000"), - (datetime.date(2024, 7, 8), "0000-0000"), - (datetime.date(2024, 7, 9), "0000-0000"), - (datetime.date(2024, 7, 10), "0000-0000"), - (datetime.date(2024, 7, 11), "0000-0000"), - (datetime.date(2024, 7, 12), "0000-1700"), - (datetime.date(2024, 7, 13), None), - (datetime.date(2024, 7, 14), "1700-0000"), - (datetime.date(2024, 7, 15), "0000-0000"), - (datetime.date(2024, 7, 16), "0000-0000"), - (datetime.date(2024, 7, 17), "0000-0000"), - (datetime.date(2024, 7, 18), "0000-0000"), - (datetime.date(2024, 7, 19), "0000-1700"), - (datetime.date(2024, 7, 20), None), - (datetime.date(2024, 7, 21), "1700-0000"), - (datetime.date(2024, 7, 22), "0000-0000"), - (datetime.date(2024, 7, 23), "0000-0000"), - (datetime.date(2024, 7, 24), "0000-0000"), - (datetime.date(2024, 7, 25), "0000-0000"), - (datetime.date(2024, 7, 26), "0000-1700"), - (datetime.date(2024, 7, 27), None), - (datetime.date(2024, 7, 28), "1700-0000"), - (datetime.date(2024, 7, 29), "0000-0000"), - (datetime.date(2024, 7, 30), "0000-0000"), - (datetime.date(2024, 7, 31), "0000-0000"), - (datetime.date(2024, 8, 1), "0000-0000"), - (datetime.date(2024, 8, 2), "0000-1700"), - (datetime.date(2024, 8, 3), None), - (datetime.date(2024, 8, 4), "1700-0000"), - (datetime.date(2024, 8, 5), "0000-0000"), - (datetime.date(2024, 8, 6), "0000-0000"), - (datetime.date(2024, 8, 7), "0000-0000"), - (datetime.date(2024, 8, 8), "0000-0000"), - (datetime.date(2024, 8, 9), "0000-1700"), - (datetime.date(2024, 8, 10), None), - (datetime.date(2024, 8, 11), "1700-0000"), - (datetime.date(2024, 8, 12), "0000-0000"), - (datetime.date(2024, 8, 13), "0000-0000"), - (datetime.date(2024, 8, 14), "0000-0000"), - (datetime.date(2024, 8, 15), "0000-0000"), - (datetime.date(2024, 8, 16), "0000-1700"), - (datetime.date(2024, 8, 17), None), - (datetime.date(2024, 8, 18), "1700-0000"), - (datetime.date(2024, 8, 19), "0000-0000"), - (datetime.date(2024, 8, 20), "0000-0000"), - (datetime.date(2024, 8, 21), "0000-0000"), - (datetime.date(2024, 8, 22), "0000-0000"), - (datetime.date(2024, 8, 23), "0000-1700"), - (datetime.date(2024, 8, 24), None), - (datetime.date(2024, 8, 25), "1700-0000"), - (datetime.date(2024, 8, 26), "0000-0000"), - (datetime.date(2024, 8, 27), "0000-0000"), - (datetime.date(2024, 8, 28), "0000-0000"), - (datetime.date(2024, 8, 29), "0000-0000"), - (datetime.date(2024, 8, 30), "0000-1700"), - (datetime.date(2024, 8, 31), None), - (datetime.date(2024, 9, 1), "1700-0000"), - (datetime.date(2024, 9, 2), "0000-0000"), - (datetime.date(2024, 9, 3), "0000-0000"), - (datetime.date(2024, 9, 4), "0000-0000"), - (datetime.date(2024, 9, 5), "0000-0000"), - (datetime.date(2024, 9, 6), "0000-1700"), - (datetime.date(2024, 9, 7), None), - (datetime.date(2024, 9, 8), "1700-0000"), - (datetime.date(2024, 9, 9), "0000-0000"), - (datetime.date(2024, 9, 10), "0000-0000"), - (datetime.date(2024, 9, 11), "0000-0000"), - (datetime.date(2024, 9, 12), "0000-0000"), - (datetime.date(2024, 9, 13), "0000-1700"), - (datetime.date(2024, 9, 14), None), - (datetime.date(2024, 9, 15), "1700-0000"), - (datetime.date(2024, 9, 16), "0000-0000"), - (datetime.date(2024, 9, 17), "0000-0000"), - (datetime.date(2024, 9, 18), "0000-0000"), - (datetime.date(2024, 9, 19), "0000-0000"), - (datetime.date(2024, 9, 20), "0000-1700"), - (datetime.date(2024, 9, 21), None), - (datetime.date(2024, 9, 22), "1700-0000"), - (datetime.date(2024, 9, 23), "0000-0000"), - (datetime.date(2024, 9, 24), "0000-0000"), - (datetime.date(2024, 9, 25), "0000-0000"), - (datetime.date(2024, 9, 26), "0000-0000"), - (datetime.date(2024, 9, 27), "0000-1700"), - (datetime.date(2024, 9, 28), None), - (datetime.date(2024, 9, 29), "1700-0000"), - (datetime.date(2024, 9, 30), "0000-0000"), - (datetime.date(2024, 10, 1), "0000-0000"), - (datetime.date(2024, 10, 2), "0000-0000"), - (datetime.date(2024, 10, 3), "0000-0000"), - (datetime.date(2024, 10, 4), "0000-1700"), - (datetime.date(2024, 10, 5), None), - (datetime.date(2024, 10, 6), "1700-0000"), - (datetime.date(2024, 10, 7), "0000-0000"), - (datetime.date(2024, 10, 8), "0000-0000"), - (datetime.date(2024, 10, 9), "0000-0000"), - (datetime.date(2024, 10, 10), "0000-0000"), - (datetime.date(2024, 10, 11), "0000-1700"), - (datetime.date(2024, 10, 12), None), - (datetime.date(2024, 10, 13), "1700-0000"), - (datetime.date(2024, 10, 14), "0000-0000"), - (datetime.date(2024, 10, 15), "0000-0000"), - (datetime.date(2024, 10, 16), "0000-0000"), - (datetime.date(2024, 10, 17), "0000-0000"), - (datetime.date(2024, 10, 18), "0000-1700"), - (datetime.date(2024, 10, 19), None), - (datetime.date(2024, 10, 20), "1700-0000"), - (datetime.date(2024, 10, 21), "0000-0000"), - (datetime.date(2024, 10, 22), "0000-0000"), - (datetime.date(2024, 10, 23), "0000-0000"), - (datetime.date(2024, 10, 24), "0000-0000"), - (datetime.date(2024, 10, 25), "0000-1700"), - (datetime.date(2024, 10, 26), None), - (datetime.date(2024, 10, 27), "1700-0000"), - (datetime.date(2024, 10, 28), "0000-0000"), - (datetime.date(2024, 10, 29), "0000-0000"), - (datetime.date(2024, 10, 30), "0000-0000"), - (datetime.date(2024, 10, 31), "0000-0000"), - (datetime.date(2024, 11, 1), "0000-1700"), - (datetime.date(2024, 11, 2), None), - (datetime.date(2024, 11, 3), "1700-0000"), - (datetime.date(2024, 11, 4), "0000-0000"), - (datetime.date(2024, 11, 5), "0000-0000"), - (datetime.date(2024, 11, 6), "0000-0000"), - (datetime.date(2024, 11, 7), "0000-0000"), - (datetime.date(2024, 11, 8), "0000-1700"), - (datetime.date(2024, 11, 9), None), - (datetime.date(2024, 11, 10), "1700-0000"), - (datetime.date(2024, 11, 11), "0000-0000"), - (datetime.date(2024, 11, 12), "0000-0000"), - (datetime.date(2024, 11, 13), "0000-0000"), - (datetime.date(2024, 11, 14), "0000-0000"), - (datetime.date(2024, 11, 15), "0000-1700"), - (datetime.date(2024, 11, 16), None), - (datetime.date(2024, 11, 17), "1700-0000"), - (datetime.date(2024, 11, 18), "0000-0000"), - (datetime.date(2024, 11, 19), "0000-0000"), - (datetime.date(2024, 11, 20), "0000-0000"), - (datetime.date(2024, 11, 21), "0000-0000"), - (datetime.date(2024, 11, 22), "0000-1700"), - (datetime.date(2024, 11, 23), None), - (datetime.date(2024, 11, 24), "1700-0000"), - (datetime.date(2024, 11, 25), "0000-0000"), - (datetime.date(2024, 11, 26), "0000-0000"), - (datetime.date(2024, 11, 27), "0000-0000"), - (datetime.date(2024, 11, 28), "0000-0000"), - (datetime.date(2024, 11, 29), "0000-1700"), - (datetime.date(2024, 11, 30), None), - (datetime.date(2024, 12, 1), "1700-0000"), - (datetime.date(2024, 12, 2), "0000-0000"), - (datetime.date(2024, 12, 3), "0000-0000"), - (datetime.date(2024, 12, 4), "0000-0000"), - (datetime.date(2024, 12, 5), "0000-0000"), - (datetime.date(2024, 12, 6), "0000-1700"), - (datetime.date(2024, 12, 7), None), - (datetime.date(2024, 12, 8), "1700-0000"), - (datetime.date(2024, 12, 9), "0000-0000"), - (datetime.date(2024, 12, 10), "0000-0000"), - (datetime.date(2024, 12, 11), "0000-0000"), - (datetime.date(2024, 12, 12), "0000-0000"), - (datetime.date(2024, 12, 13), "0000-1700"), - (datetime.date(2024, 12, 14), None), - (datetime.date(2024, 12, 15), "1700-0000"), - (datetime.date(2024, 12, 16), "0000-0000"), - (datetime.date(2024, 12, 17), "0000-0000"), - (datetime.date(2024, 12, 18), "0000-0000"), - (datetime.date(2024, 12, 19), "0000-0000"), - (datetime.date(2024, 12, 20), "0000-1700"), - (datetime.date(2024, 12, 21), None), - (datetime.date(2024, 12, 22), "1700-0000"), - (datetime.date(2024, 12, 23), "0000-0000"), - (datetime.date(2024, 12, 24), "0000-1700"), - (datetime.date(2024, 12, 25), "1700-0000"), - (datetime.date(2024, 12, 26), "0000-0000"), - (datetime.date(2024, 12, 27), "0000-1700"), - (datetime.date(2024, 12, 28), None), - (datetime.date(2024, 12, 29), "1700-0000"), - (datetime.date(2024, 12, 30), "0000-0000"), - (datetime.date(2024, 12, 31), "0000-0000"), -] - -METAL_2024_INTERVALS = [ - (datetime.date(2023, 12, 31), None), - (datetime.date(2024, 1, 1), "1700-0000"), - (datetime.date(2024, 1, 2), "0000-0000"), - (datetime.date(2024, 1, 3), "0000-0000"), - (datetime.date(2024, 1, 4), "0000-0000"), - (datetime.date(2024, 1, 5), "0000-1700"), - (datetime.date(2024, 1, 6), None), - (datetime.date(2024, 1, 7), "1700-0000"), - (datetime.date(2024, 1, 8), "0000-0000"), - (datetime.date(2024, 1, 9), "0000-0000"), - (datetime.date(2024, 1, 10), "0000-0000"), - (datetime.date(2024, 1, 11), "0000-0000"), - (datetime.date(2024, 1, 12), "0000-1700"), - (datetime.date(2024, 1, 13), None), - (datetime.date(2024, 1, 14), "1700-0000"), - (datetime.date(2024, 1, 15), "0000-1430"), - (datetime.date(2024, 1, 15), "1800-0000"), - (datetime.date(2024, 1, 16), "0000-0000"), - (datetime.date(2024, 1, 17), "0000-0000"), - (datetime.date(2024, 1, 18), "0000-0000"), - (datetime.date(2024, 1, 19), "0000-1700"), - (datetime.date(2024, 1, 20), None), - (datetime.date(2024, 1, 21), "1700-0000"), - (datetime.date(2024, 1, 22), "0000-0000"), - (datetime.date(2024, 1, 23), "0000-0000"), - (datetime.date(2024, 1, 24), "0000-0000"), - (datetime.date(2024, 1, 25), "0000-0000"), - (datetime.date(2024, 1, 26), "0000-1700"), - (datetime.date(2024, 1, 27), None), - (datetime.date(2024, 1, 28), "1700-0000"), - (datetime.date(2024, 1, 29), "0000-0000"), - (datetime.date(2024, 1, 30), "0000-0000"), - (datetime.date(2024, 1, 31), "0000-0000"), - (datetime.date(2024, 2, 1), "0000-0000"), - (datetime.date(2024, 2, 2), "0000-1700"), - (datetime.date(2024, 2, 3), None), - (datetime.date(2024, 2, 4), "1700-0000"), - (datetime.date(2024, 2, 5), "0000-0000"), - (datetime.date(2024, 2, 6), "0000-0000"), - (datetime.date(2024, 2, 7), "0000-0000"), - (datetime.date(2024, 2, 8), "0000-0000"), - (datetime.date(2024, 2, 9), "0000-1700"), - (datetime.date(2024, 2, 10), None), - (datetime.date(2024, 2, 11), "1700-0000"), - (datetime.date(2024, 2, 12), "0000-0000"), - (datetime.date(2024, 2, 13), "0000-0000"), - (datetime.date(2024, 2, 14), "0000-0000"), - (datetime.date(2024, 2, 15), "0000-0000"), - (datetime.date(2024, 2, 16), "0000-1700"), - (datetime.date(2024, 2, 17), None), - (datetime.date(2024, 2, 18), "1700-0000"), - (datetime.date(2024, 2, 19), "0000-1430"), - (datetime.date(2024, 2, 19), "1800-0000"), - (datetime.date(2024, 2, 20), "0000-0000"), - (datetime.date(2024, 2, 21), "0000-0000"), - (datetime.date(2024, 2, 22), "0000-0000"), - (datetime.date(2024, 2, 23), "0000-1700"), - (datetime.date(2024, 2, 24), None), - (datetime.date(2024, 2, 25), "1700-0000"), - (datetime.date(2024, 2, 26), "0000-0000"), - (datetime.date(2024, 2, 27), "0000-0000"), - (datetime.date(2024, 2, 28), "0000-0000"), - (datetime.date(2024, 2, 29), "0000-0000"), - (datetime.date(2024, 3, 1), "0000-1700"), - (datetime.date(2024, 3, 2), None), - (datetime.date(2024, 3, 3), "1700-0000"), - (datetime.date(2024, 3, 4), "0000-0000"), - (datetime.date(2024, 3, 5), "0000-0000"), - (datetime.date(2024, 3, 6), "0000-0000"), - (datetime.date(2024, 3, 7), "0000-0000"), - (datetime.date(2024, 3, 8), "0000-1700"), - (datetime.date(2024, 3, 9), None), - (datetime.date(2024, 3, 10), "1700-0000"), - (datetime.date(2024, 3, 11), "0000-0000"), - (datetime.date(2024, 3, 12), "0000-0000"), - (datetime.date(2024, 3, 13), "0000-0000"), - (datetime.date(2024, 3, 14), "0000-0000"), - (datetime.date(2024, 3, 15), "0000-1700"), - (datetime.date(2024, 3, 16), None), - (datetime.date(2024, 3, 17), "1700-0000"), - (datetime.date(2024, 3, 18), "0000-0000"), - (datetime.date(2024, 3, 19), "0000-0000"), - (datetime.date(2024, 3, 20), "0000-0000"), - (datetime.date(2024, 3, 21), "0000-0000"), - (datetime.date(2024, 3, 22), "0000-1700"), - (datetime.date(2024, 3, 23), None), - (datetime.date(2024, 3, 24), "1700-0000"), - (datetime.date(2024, 3, 25), "0000-0000"), - (datetime.date(2024, 3, 26), "0000-0000"), - (datetime.date(2024, 3, 27), "0000-0000"), - (datetime.date(2024, 3, 28), "0000-0000"), - (datetime.date(2024, 3, 29), "0000-1700"), - (datetime.date(2024, 3, 30), None), - (datetime.date(2024, 3, 31), "1700-0000"), - (datetime.date(2024, 4, 1), "0000-0000"), - (datetime.date(2024, 4, 2), "0000-0000"), - (datetime.date(2024, 4, 3), "0000-0000"), - (datetime.date(2024, 4, 4), "0000-0000"), - (datetime.date(2024, 4, 5), "0000-1700"), - (datetime.date(2024, 4, 6), None), - (datetime.date(2024, 4, 7), "1700-0000"), - (datetime.date(2024, 4, 8), "0000-0000"), - (datetime.date(2024, 4, 9), "0000-0000"), - (datetime.date(2024, 4, 10), "0000-0000"), - (datetime.date(2024, 4, 11), "0000-0000"), - (datetime.date(2024, 4, 12), "0000-1700"), - (datetime.date(2024, 4, 13), None), - (datetime.date(2024, 4, 14), "1700-0000"), - (datetime.date(2024, 4, 15), "0000-0000"), - (datetime.date(2024, 4, 16), "0000-0000"), - (datetime.date(2024, 4, 17), "0000-0000"), - (datetime.date(2024, 4, 18), "0000-0000"), - (datetime.date(2024, 4, 19), "0000-1700"), - (datetime.date(2024, 4, 20), None), - (datetime.date(2024, 4, 21), "1700-0000"), - (datetime.date(2024, 4, 22), "0000-0000"), - (datetime.date(2024, 4, 23), "0000-0000"), - (datetime.date(2024, 4, 24), "0000-0000"), - (datetime.date(2024, 4, 25), "0000-0000"), - (datetime.date(2024, 4, 26), "0000-1700"), - (datetime.date(2024, 4, 27), None), - (datetime.date(2024, 4, 28), "1700-0000"), - (datetime.date(2024, 4, 29), "0000-0000"), - (datetime.date(2024, 4, 30), "0000-0000"), - (datetime.date(2024, 5, 1), "0000-0000"), - (datetime.date(2024, 5, 2), "0000-0000"), - (datetime.date(2024, 5, 3), "0000-1700"), - (datetime.date(2024, 5, 4), None), - (datetime.date(2024, 5, 5), "1700-0000"), - (datetime.date(2024, 5, 6), "0000-0000"), - (datetime.date(2024, 5, 7), "0000-0000"), - (datetime.date(2024, 5, 8), "0000-0000"), - (datetime.date(2024, 5, 9), "0000-0000"), - (datetime.date(2024, 5, 10), "0000-1700"), - (datetime.date(2024, 5, 11), None), - (datetime.date(2024, 5, 12), "1700-0000"), - (datetime.date(2024, 5, 13), "0000-0000"), - (datetime.date(2024, 5, 14), "0000-0000"), - (datetime.date(2024, 5, 15), "0000-0000"), - (datetime.date(2024, 5, 16), "0000-0000"), - (datetime.date(2024, 5, 17), "0000-1700"), - (datetime.date(2024, 5, 18), None), - (datetime.date(2024, 5, 19), "1700-0000"), - (datetime.date(2024, 5, 20), "0000-0000"), - (datetime.date(2024, 5, 21), "0000-0000"), - (datetime.date(2024, 5, 22), "0000-0000"), - (datetime.date(2024, 5, 23), "0000-0000"), - (datetime.date(2024, 5, 24), "0000-1700"), - (datetime.date(2024, 5, 25), None), - (datetime.date(2024, 5, 26), "1700-0000"), - (datetime.date(2024, 5, 27), "0000-0000"), - (datetime.date(2024, 5, 28), "0000-0000"), - (datetime.date(2024, 5, 29), "0000-0000"), - (datetime.date(2024, 5, 30), "0000-0000"), - (datetime.date(2024, 5, 31), "0000-1700"), - (datetime.date(2024, 6, 1), None), - (datetime.date(2024, 6, 2), "1700-0000"), - (datetime.date(2024, 6, 3), "0000-0000"), - (datetime.date(2024, 6, 4), "0000-0000"), - (datetime.date(2024, 6, 5), "0000-0000"), - (datetime.date(2024, 6, 6), "0000-0000"), - (datetime.date(2024, 6, 7), "0000-1700"), - (datetime.date(2024, 6, 8), None), - (datetime.date(2024, 6, 9), "1700-0000"), - (datetime.date(2024, 6, 10), "0000-0000"), - (datetime.date(2024, 6, 11), "0000-0000"), - (datetime.date(2024, 6, 12), "0000-0000"), - (datetime.date(2024, 6, 13), "0000-0000"), - (datetime.date(2024, 6, 14), "0000-1700"), - (datetime.date(2024, 6, 15), None), - (datetime.date(2024, 6, 16), "1700-0000"), - (datetime.date(2024, 6, 17), "0000-0000"), - (datetime.date(2024, 6, 18), "0000-0000"), - (datetime.date(2024, 6, 19), "0000-0000"), - (datetime.date(2024, 6, 20), "0000-0000"), - (datetime.date(2024, 6, 21), "0000-1700"), - (datetime.date(2024, 6, 22), None), - (datetime.date(2024, 6, 23), "1700-0000"), - (datetime.date(2024, 6, 24), "0000-0000"), - (datetime.date(2024, 6, 25), "0000-0000"), - (datetime.date(2024, 6, 26), "0000-0000"), - (datetime.date(2024, 6, 27), "0000-0000"), - (datetime.date(2024, 6, 28), "0000-1700"), - (datetime.date(2024, 6, 29), None), - (datetime.date(2024, 6, 30), "1700-0000"), - (datetime.date(2024, 7, 1), "0000-0000"), - (datetime.date(2024, 7, 2), "0000-0000"), - (datetime.date(2024, 7, 3), "0000-0000"), - (datetime.date(2024, 7, 4), "0000-0000"), - (datetime.date(2024, 7, 5), "0000-1700"), - (datetime.date(2024, 7, 6), None), - (datetime.date(2024, 7, 7), "1700-0000"), - (datetime.date(2024, 7, 8), "0000-0000"), - (datetime.date(2024, 7, 9), "0000-0000"), - (datetime.date(2024, 7, 10), "0000-0000"), - (datetime.date(2024, 7, 11), "0000-0000"), - (datetime.date(2024, 7, 12), "0000-1700"), - (datetime.date(2024, 7, 13), None), - (datetime.date(2024, 7, 14), "1700-0000"), - (datetime.date(2024, 7, 15), "0000-0000"), - (datetime.date(2024, 7, 16), "0000-0000"), - (datetime.date(2024, 7, 17), "0000-0000"), - (datetime.date(2024, 7, 18), "0000-0000"), - (datetime.date(2024, 7, 19), "0000-1700"), - (datetime.date(2024, 7, 20), None), - (datetime.date(2024, 7, 21), "1700-0000"), - (datetime.date(2024, 7, 22), "0000-0000"), - (datetime.date(2024, 7, 23), "0000-0000"), - (datetime.date(2024, 7, 24), "0000-0000"), - (datetime.date(2024, 7, 25), "0000-0000"), - (datetime.date(2024, 7, 26), "0000-1700"), - (datetime.date(2024, 7, 27), None), - (datetime.date(2024, 7, 28), "1700-0000"), - (datetime.date(2024, 7, 29), "0000-0000"), - (datetime.date(2024, 7, 30), "0000-0000"), - (datetime.date(2024, 7, 31), "0000-0000"), - (datetime.date(2024, 8, 1), "0000-0000"), - (datetime.date(2024, 8, 2), "0000-1700"), - (datetime.date(2024, 8, 3), None), - (datetime.date(2024, 8, 4), "1700-0000"), - (datetime.date(2024, 8, 5), "0000-0000"), - (datetime.date(2024, 8, 6), "0000-0000"), - (datetime.date(2024, 8, 7), "0000-0000"), - (datetime.date(2024, 8, 8), "0000-0000"), - (datetime.date(2024, 8, 9), "0000-1700"), - (datetime.date(2024, 8, 10), None), - (datetime.date(2024, 8, 11), "1700-0000"), - (datetime.date(2024, 8, 12), "0000-0000"), - (datetime.date(2024, 8, 13), "0000-0000"), - (datetime.date(2024, 8, 14), "0000-0000"), - (datetime.date(2024, 8, 15), "0000-0000"), - (datetime.date(2024, 8, 16), "0000-1700"), - (datetime.date(2024, 8, 17), None), - (datetime.date(2024, 8, 18), "1700-0000"), - (datetime.date(2024, 8, 19), "0000-0000"), - (datetime.date(2024, 8, 20), "0000-0000"), - (datetime.date(2024, 8, 21), "0000-0000"), - (datetime.date(2024, 8, 22), "0000-0000"), - (datetime.date(2024, 8, 23), "0000-1700"), - (datetime.date(2024, 8, 24), None), - (datetime.date(2024, 8, 25), "1700-0000"), - (datetime.date(2024, 8, 26), "0000-0000"), - (datetime.date(2024, 8, 27), "0000-0000"), - (datetime.date(2024, 8, 28), "0000-0000"), - (datetime.date(2024, 8, 29), "0000-0000"), - (datetime.date(2024, 8, 30), "0000-1700"), - (datetime.date(2024, 8, 31), None), - (datetime.date(2024, 9, 1), "1700-0000"), - (datetime.date(2024, 9, 2), "0000-0000"), - (datetime.date(2024, 9, 3), "0000-0000"), - (datetime.date(2024, 9, 4), "0000-0000"), - (datetime.date(2024, 9, 5), "0000-0000"), - (datetime.date(2024, 9, 6), "0000-1700"), - (datetime.date(2024, 9, 7), None), - (datetime.date(2024, 9, 8), "1700-0000"), - (datetime.date(2024, 9, 9), "0000-0000"), - (datetime.date(2024, 9, 10), "0000-0000"), - (datetime.date(2024, 9, 11), "0000-0000"), - (datetime.date(2024, 9, 12), "0000-0000"), - (datetime.date(2024, 9, 13), "0000-1700"), - (datetime.date(2024, 9, 14), None), - (datetime.date(2024, 9, 15), "1700-0000"), - (datetime.date(2024, 9, 16), "0000-0000"), - (datetime.date(2024, 9, 17), "0000-0000"), - (datetime.date(2024, 9, 18), "0000-0000"), - (datetime.date(2024, 9, 19), "0000-0000"), - (datetime.date(2024, 9, 20), "0000-1700"), - (datetime.date(2024, 9, 21), None), - (datetime.date(2024, 9, 22), "1700-0000"), - (datetime.date(2024, 9, 23), "0000-0000"), - (datetime.date(2024, 9, 24), "0000-0000"), - (datetime.date(2024, 9, 25), "0000-0000"), - (datetime.date(2024, 9, 26), "0000-0000"), - (datetime.date(2024, 9, 27), "0000-1700"), - (datetime.date(2024, 9, 28), None), - (datetime.date(2024, 9, 29), "1700-0000"), - (datetime.date(2024, 9, 30), "0000-0000"), - (datetime.date(2024, 10, 1), "0000-0000"), - (datetime.date(2024, 10, 2), "0000-0000"), - (datetime.date(2024, 10, 3), "0000-0000"), - (datetime.date(2024, 10, 4), "0000-1700"), - (datetime.date(2024, 10, 5), None), - (datetime.date(2024, 10, 6), "1700-0000"), - (datetime.date(2024, 10, 7), "0000-0000"), - (datetime.date(2024, 10, 8), "0000-0000"), - (datetime.date(2024, 10, 9), "0000-0000"), - (datetime.date(2024, 10, 10), "0000-0000"), - (datetime.date(2024, 10, 11), "0000-1700"), - (datetime.date(2024, 10, 12), None), - (datetime.date(2024, 10, 13), "1700-0000"), - (datetime.date(2024, 10, 14), "0000-0000"), - (datetime.date(2024, 10, 15), "0000-0000"), - (datetime.date(2024, 10, 16), "0000-0000"), - (datetime.date(2024, 10, 17), "0000-0000"), - (datetime.date(2024, 10, 18), "0000-1700"), - (datetime.date(2024, 10, 19), None), - (datetime.date(2024, 10, 20), "1700-0000"), - (datetime.date(2024, 10, 21), "0000-0000"), - (datetime.date(2024, 10, 22), "0000-0000"), - (datetime.date(2024, 10, 23), "0000-0000"), - (datetime.date(2024, 10, 24), "0000-0000"), - (datetime.date(2024, 10, 25), "0000-1700"), - (datetime.date(2024, 10, 26), None), - (datetime.date(2024, 10, 27), "1700-0000"), - (datetime.date(2024, 10, 28), "0000-0000"), - (datetime.date(2024, 10, 29), "0000-0000"), - (datetime.date(2024, 10, 30), "0000-0000"), - (datetime.date(2024, 10, 31), "0000-0000"), - (datetime.date(2024, 11, 1), "0000-1700"), - (datetime.date(2024, 11, 2), None), - (datetime.date(2024, 11, 3), "1700-0000"), - (datetime.date(2024, 11, 4), "0000-0000"), - (datetime.date(2024, 11, 5), "0000-0000"), - (datetime.date(2024, 11, 6), "0000-0000"), - (datetime.date(2024, 11, 7), "0000-0000"), - (datetime.date(2024, 11, 8), "0000-1700"), - (datetime.date(2024, 11, 9), None), - (datetime.date(2024, 11, 10), "1700-0000"), - (datetime.date(2024, 11, 11), "0000-0000"), - (datetime.date(2024, 11, 12), "0000-0000"), - (datetime.date(2024, 11, 13), "0000-0000"), - (datetime.date(2024, 11, 14), "0000-0000"), - (datetime.date(2024, 11, 15), "0000-1700"), - (datetime.date(2024, 11, 16), None), - (datetime.date(2024, 11, 17), "1700-0000"), - (datetime.date(2024, 11, 18), "0000-0000"), - (datetime.date(2024, 11, 19), "0000-0000"), - (datetime.date(2024, 11, 20), "0000-0000"), - (datetime.date(2024, 11, 21), "0000-0000"), - (datetime.date(2024, 11, 22), "0000-1700"), - (datetime.date(2024, 11, 23), None), - (datetime.date(2024, 11, 24), "1700-0000"), - (datetime.date(2024, 11, 25), "0000-0000"), - (datetime.date(2024, 11, 26), "0000-0000"), - (datetime.date(2024, 11, 27), "0000-0000"), - (datetime.date(2024, 11, 28), "0000-0000"), - (datetime.date(2024, 11, 29), "0000-1700"), - (datetime.date(2024, 11, 30), None), - (datetime.date(2024, 12, 1), "1700-0000"), - (datetime.date(2024, 12, 2), "0000-0000"), - (datetime.date(2024, 12, 3), "0000-0000"), - (datetime.date(2024, 12, 4), "0000-0000"), - (datetime.date(2024, 12, 5), "0000-0000"), - (datetime.date(2024, 12, 6), "0000-1700"), - (datetime.date(2024, 12, 7), None), - (datetime.date(2024, 12, 8), "1700-0000"), - (datetime.date(2024, 12, 9), "0000-0000"), - (datetime.date(2024, 12, 10), "0000-0000"), - (datetime.date(2024, 12, 11), "0000-0000"), - (datetime.date(2024, 12, 12), "0000-0000"), - (datetime.date(2024, 12, 13), "0000-1700"), - (datetime.date(2024, 12, 14), None), - (datetime.date(2024, 12, 15), "1700-0000"), - (datetime.date(2024, 12, 16), "0000-0000"), - (datetime.date(2024, 12, 17), "0000-0000"), - (datetime.date(2024, 12, 18), "0000-0000"), - (datetime.date(2024, 12, 19), "0000-0000"), - (datetime.date(2024, 12, 20), "0000-1700"), - (datetime.date(2024, 12, 21), None), - (datetime.date(2024, 12, 22), "1700-0000"), - (datetime.date(2024, 12, 23), "0000-0000"), - (datetime.date(2024, 12, 24), "0000-1700"), - (datetime.date(2024, 12, 25), "1700-0000"), - (datetime.date(2024, 12, 26), "0000-0000"), - (datetime.date(2024, 12, 27), "0000-1700"), - (datetime.date(2024, 12, 28), None), - (datetime.date(2024, 12, 29), "1700-0000"), - (datetime.date(2024, 12, 30), "0000-0000"), - (datetime.date(2024, 12, 31), "0000-0000"), -] - -RATES_2024_INTERVALS = [ - (datetime.date(2024, 1, 1), None), - (datetime.date(2024, 1, 2), "0800-1700"), - (datetime.date(2024, 1, 3), "0800-1700"), - (datetime.date(2024, 1, 4), "0800-1700"), - (datetime.date(2024, 1, 5), "0800-1700"), - (datetime.date(2024, 1, 6), None), - (datetime.date(2024, 1, 7), None), - (datetime.date(2024, 1, 8), "0800-1700"), - (datetime.date(2024, 1, 9), "0800-1700"), - (datetime.date(2024, 1, 10), "0800-1700"), - (datetime.date(2024, 1, 11), "0800-1700"), - (datetime.date(2024, 1, 12), "0800-1700"), - (datetime.date(2024, 1, 13), None), - (datetime.date(2024, 1, 14), None), - (datetime.date(2024, 1, 15), None), - (datetime.date(2024, 1, 16), "0800-1700"), - (datetime.date(2024, 1, 17), "0800-1700"), - (datetime.date(2024, 1, 18), "0800-1700"), - (datetime.date(2024, 1, 19), "0800-1700"), - (datetime.date(2024, 1, 20), None), - (datetime.date(2024, 1, 21), None), - (datetime.date(2024, 1, 22), "0800-1700"), - (datetime.date(2024, 1, 23), "0800-1700"), - (datetime.date(2024, 1, 24), "0800-1700"), - (datetime.date(2024, 1, 25), "0800-1700"), - (datetime.date(2024, 1, 26), "0800-1700"), - (datetime.date(2024, 1, 27), None), - (datetime.date(2024, 1, 28), None), - (datetime.date(2024, 1, 29), "0800-1700"), - (datetime.date(2024, 1, 30), "0800-1700"), - (datetime.date(2024, 1, 31), "0800-1700"), - (datetime.date(2024, 2, 1), "0800-1700"), - (datetime.date(2024, 2, 2), "0800-1700"), - (datetime.date(2024, 2, 3), None), - (datetime.date(2024, 2, 4), None), - (datetime.date(2024, 2, 5), "0800-1700"), - (datetime.date(2024, 2, 6), "0800-1700"), - (datetime.date(2024, 2, 7), "0800-1700"), - (datetime.date(2024, 2, 8), "0800-1700"), - (datetime.date(2024, 2, 9), "0800-1700"), - (datetime.date(2024, 2, 10), None), - (datetime.date(2024, 2, 11), None), - (datetime.date(2024, 2, 12), "0800-1700"), - (datetime.date(2024, 2, 13), "0800-1700"), - (datetime.date(2024, 2, 14), "0800-1700"), - (datetime.date(2024, 2, 15), "0800-1700"), - (datetime.date(2024, 2, 16), "0800-1700"), - (datetime.date(2024, 2, 17), None), - (datetime.date(2024, 2, 18), None), - (datetime.date(2024, 2, 19), None), - (datetime.date(2024, 2, 20), "0800-1700"), - (datetime.date(2024, 2, 21), "0800-1700"), - (datetime.date(2024, 2, 22), "0800-1700"), - (datetime.date(2024, 2, 23), "0800-1700"), - (datetime.date(2024, 2, 24), None), - (datetime.date(2024, 2, 25), None), - (datetime.date(2024, 2, 26), "0800-1700"), - (datetime.date(2024, 2, 27), "0800-1700"), - (datetime.date(2024, 2, 28), "0800-1700"), - (datetime.date(2024, 2, 29), "0800-1700"), - (datetime.date(2024, 3, 1), "0800-1700"), - (datetime.date(2024, 3, 2), None), - (datetime.date(2024, 3, 3), None), - (datetime.date(2024, 3, 4), "0800-1700"), - (datetime.date(2024, 3, 5), "0800-1700"), - (datetime.date(2024, 3, 6), "0800-1700"), - (datetime.date(2024, 3, 7), "0800-1700"), - (datetime.date(2024, 3, 8), "0800-1700"), - (datetime.date(2024, 3, 9), None), - (datetime.date(2024, 3, 10), None), - (datetime.date(2024, 3, 11), "0800-1700"), - (datetime.date(2024, 3, 12), "0800-1700"), - (datetime.date(2024, 3, 13), "0800-1700"), - (datetime.date(2024, 3, 14), "0800-1700"), - (datetime.date(2024, 3, 15), "0800-1700"), - (datetime.date(2024, 3, 16), None), - (datetime.date(2024, 3, 17), None), - (datetime.date(2024, 3, 18), "0800-1700"), - (datetime.date(2024, 3, 19), "0800-1700"), - (datetime.date(2024, 3, 20), "0800-1700"), - (datetime.date(2024, 3, 21), "0800-1700"), - (datetime.date(2024, 3, 22), "0800-1700"), - (datetime.date(2024, 3, 23), None), - (datetime.date(2024, 3, 24), None), - (datetime.date(2024, 3, 25), "0800-1700"), - (datetime.date(2024, 3, 26), "0800-1700"), - (datetime.date(2024, 3, 27), "0800-1700"), - (datetime.date(2024, 3, 28), "0800-1700"), - (datetime.date(2024, 3, 29), None), - (datetime.date(2024, 3, 30), None), - (datetime.date(2024, 3, 31), None), - (datetime.date(2024, 4, 1), "0800-1700"), - (datetime.date(2024, 4, 2), "0800-1700"), - (datetime.date(2024, 4, 3), "0800-1700"), - (datetime.date(2024, 4, 4), "0800-1700"), - (datetime.date(2024, 4, 5), "0800-1700"), - (datetime.date(2024, 4, 6), None), - (datetime.date(2024, 4, 7), None), - (datetime.date(2024, 4, 8), "0800-1700"), - (datetime.date(2024, 4, 9), "0800-1700"), - (datetime.date(2024, 4, 10), "0800-1700"), - (datetime.date(2024, 4, 11), "0800-1700"), - (datetime.date(2024, 4, 12), "0800-1700"), - (datetime.date(2024, 4, 13), None), - (datetime.date(2024, 4, 14), None), - (datetime.date(2024, 4, 15), "0800-1700"), - (datetime.date(2024, 4, 16), "0800-1700"), - (datetime.date(2024, 4, 17), "0800-1700"), - (datetime.date(2024, 4, 18), "0800-1700"), - (datetime.date(2024, 4, 19), "0800-1700"), - (datetime.date(2024, 4, 20), None), - (datetime.date(2024, 4, 21), None), - (datetime.date(2024, 4, 22), "0800-1700"), - (datetime.date(2024, 4, 23), "0800-1700"), - (datetime.date(2024, 4, 24), "0800-1700"), - (datetime.date(2024, 4, 25), "0800-1700"), - (datetime.date(2024, 4, 26), "0800-1700"), - (datetime.date(2024, 4, 27), None), - (datetime.date(2024, 4, 28), None), - (datetime.date(2024, 4, 29), "0800-1700"), - (datetime.date(2024, 4, 30), "0800-1700"), - (datetime.date(2024, 5, 1), "0800-1700"), - (datetime.date(2024, 5, 2), "0800-1700"), - (datetime.date(2024, 5, 3), "0800-1700"), - (datetime.date(2024, 5, 4), None), - (datetime.date(2024, 5, 5), None), - (datetime.date(2024, 5, 6), "0800-1700"), - (datetime.date(2024, 5, 7), "0800-1700"), - (datetime.date(2024, 5, 8), "0800-1700"), - (datetime.date(2024, 5, 9), "0800-1700"), - (datetime.date(2024, 5, 10), "0800-1700"), - (datetime.date(2024, 5, 11), None), - (datetime.date(2024, 5, 12), None), - (datetime.date(2024, 5, 13), "0800-1700"), - (datetime.date(2024, 5, 14), "0800-1700"), - (datetime.date(2024, 5, 15), "0800-1700"), - (datetime.date(2024, 5, 16), "0800-1700"), - (datetime.date(2024, 5, 17), "0800-1700"), - (datetime.date(2024, 5, 18), None), - (datetime.date(2024, 5, 19), None), - (datetime.date(2024, 5, 20), "0800-1700"), - (datetime.date(2024, 5, 21), "0800-1700"), - (datetime.date(2024, 5, 22), "0800-1700"), - (datetime.date(2024, 5, 23), "0800-1700"), - (datetime.date(2024, 5, 24), "0800-1700"), - (datetime.date(2024, 5, 25), None), - (datetime.date(2024, 5, 26), None), - (datetime.date(2024, 5, 27), None), - (datetime.date(2024, 5, 28), "0800-1700"), - (datetime.date(2024, 5, 29), "0800-1700"), - (datetime.date(2024, 5, 30), "0800-1700"), - (datetime.date(2024, 5, 31), "0800-1700"), - (datetime.date(2024, 6, 1), None), - (datetime.date(2024, 6, 2), None), - (datetime.date(2024, 6, 3), "0800-1700"), - (datetime.date(2024, 6, 4), "0800-1700"), - (datetime.date(2024, 6, 5), "0800-1700"), - (datetime.date(2024, 6, 6), "0800-1700"), - (datetime.date(2024, 6, 7), "0800-1700"), - (datetime.date(2024, 6, 8), None), - (datetime.date(2024, 6, 9), None), - (datetime.date(2024, 6, 10), "0800-1700"), - (datetime.date(2024, 6, 11), "0800-1700"), - (datetime.date(2024, 6, 12), "0800-1700"), - (datetime.date(2024, 6, 13), "0800-1700"), - (datetime.date(2024, 6, 14), "0800-1700"), - (datetime.date(2024, 6, 15), None), - (datetime.date(2024, 6, 16), None), - (datetime.date(2024, 6, 17), "0800-1700"), - (datetime.date(2024, 6, 18), "0800-1700"), - (datetime.date(2024, 6, 19), None), - (datetime.date(2024, 6, 20), "0800-1700"), - (datetime.date(2024, 6, 21), "0800-1700"), - (datetime.date(2024, 6, 22), None), - (datetime.date(2024, 6, 23), None), - (datetime.date(2024, 6, 24), "0800-1700"), - (datetime.date(2024, 6, 25), "0800-1700"), - (datetime.date(2024, 6, 26), "0800-1700"), - (datetime.date(2024, 6, 27), "0800-1700"), - (datetime.date(2024, 6, 28), "0800-1700"), - (datetime.date(2024, 6, 29), None), - (datetime.date(2024, 6, 30), None), - (datetime.date(2024, 7, 1), "0800-1700"), - (datetime.date(2024, 7, 2), "0800-1700"), - (datetime.date(2024, 7, 3), "0800-1300"), - (datetime.date(2024, 7, 4), None), - (datetime.date(2024, 7, 5), "0800-1700"), - (datetime.date(2024, 7, 6), None), - (datetime.date(2024, 7, 7), None), - (datetime.date(2024, 7, 8), "0800-1700"), - (datetime.date(2024, 7, 9), "0800-1700"), - (datetime.date(2024, 7, 10), "0800-1700"), - (datetime.date(2024, 7, 11), "0800-1700"), - (datetime.date(2024, 7, 12), "0800-1700"), - (datetime.date(2024, 7, 13), None), - (datetime.date(2024, 7, 14), None), - (datetime.date(2024, 7, 15), "0800-1700"), - (datetime.date(2024, 7, 16), "0800-1700"), - (datetime.date(2024, 7, 17), "0800-1700"), - (datetime.date(2024, 7, 18), "0800-1700"), - (datetime.date(2024, 7, 19), "0800-1700"), - (datetime.date(2024, 7, 20), None), - (datetime.date(2024, 7, 21), None), - (datetime.date(2024, 7, 22), "0800-1700"), - (datetime.date(2024, 7, 23), "0800-1700"), - (datetime.date(2024, 7, 24), "0800-1700"), - (datetime.date(2024, 7, 25), "0800-1700"), - (datetime.date(2024, 7, 26), "0800-1700"), - (datetime.date(2024, 7, 27), None), - (datetime.date(2024, 7, 28), None), - (datetime.date(2024, 7, 29), "0800-1700"), - (datetime.date(2024, 7, 30), "0800-1700"), - (datetime.date(2024, 7, 31), "0800-1700"), - (datetime.date(2024, 8, 1), "0800-1700"), - (datetime.date(2024, 8, 2), "0800-1700"), - (datetime.date(2024, 8, 3), None), - (datetime.date(2024, 8, 4), None), - (datetime.date(2024, 8, 5), "0800-1700"), - (datetime.date(2024, 8, 6), "0800-1700"), - (datetime.date(2024, 8, 7), "0800-1700"), - (datetime.date(2024, 8, 8), "0800-1700"), - (datetime.date(2024, 8, 9), "0800-1700"), - (datetime.date(2024, 8, 10), None), - (datetime.date(2024, 8, 11), None), - (datetime.date(2024, 8, 12), "0800-1700"), - (datetime.date(2024, 8, 13), "0800-1700"), - (datetime.date(2024, 8, 14), "0800-1700"), - (datetime.date(2024, 8, 15), "0800-1700"), - (datetime.date(2024, 8, 16), "0800-1700"), - (datetime.date(2024, 8, 17), None), - (datetime.date(2024, 8, 18), None), - (datetime.date(2024, 8, 19), "0800-1700"), - (datetime.date(2024, 8, 20), "0800-1700"), - (datetime.date(2024, 8, 21), "0800-1700"), - (datetime.date(2024, 8, 22), "0800-1700"), - (datetime.date(2024, 8, 23), "0800-1700"), - (datetime.date(2024, 8, 24), None), - (datetime.date(2024, 8, 25), None), - (datetime.date(2024, 8, 26), "0800-1700"), - (datetime.date(2024, 8, 27), "0800-1700"), - (datetime.date(2024, 8, 28), "0800-1700"), - (datetime.date(2024, 8, 29), "0800-1700"), - (datetime.date(2024, 8, 30), "0800-1700"), - (datetime.date(2024, 8, 31), None), - (datetime.date(2024, 9, 1), None), - (datetime.date(2024, 9, 2), None), - (datetime.date(2024, 9, 3), "0800-1700"), - (datetime.date(2024, 9, 4), "0800-1700"), - (datetime.date(2024, 9, 5), "0800-1700"), - (datetime.date(2024, 9, 6), "0800-1700"), - (datetime.date(2024, 9, 7), None), - (datetime.date(2024, 9, 8), None), - (datetime.date(2024, 9, 9), "0800-1700"), - (datetime.date(2024, 9, 10), "0800-1700"), - (datetime.date(2024, 9, 11), "0800-1700"), - (datetime.date(2024, 9, 12), "0800-1700"), - (datetime.date(2024, 9, 13), "0800-1700"), - (datetime.date(2024, 9, 14), None), - (datetime.date(2024, 9, 15), None), - (datetime.date(2024, 9, 16), "0800-1700"), - (datetime.date(2024, 9, 17), "0800-1700"), - (datetime.date(2024, 9, 18), "0800-1700"), - (datetime.date(2024, 9, 19), "0800-1700"), - (datetime.date(2024, 9, 20), "0800-1700"), - (datetime.date(2024, 9, 21), None), - (datetime.date(2024, 9, 22), None), - (datetime.date(2024, 9, 23), "0800-1700"), - (datetime.date(2024, 9, 24), "0800-1700"), - (datetime.date(2024, 9, 25), "0800-1700"), - (datetime.date(2024, 9, 26), "0800-1700"), - (datetime.date(2024, 9, 27), "0800-1700"), - (datetime.date(2024, 9, 28), None), - (datetime.date(2024, 9, 29), None), - (datetime.date(2024, 9, 30), "0800-1700"), - (datetime.date(2024, 10, 1), "0800-1700"), - (datetime.date(2024, 10, 2), "0800-1700"), - (datetime.date(2024, 10, 3), "0800-1700"), - (datetime.date(2024, 10, 4), "0800-1700"), - (datetime.date(2024, 10, 5), None), - (datetime.date(2024, 10, 6), None), - (datetime.date(2024, 10, 7), "0800-1700"), - (datetime.date(2024, 10, 8), "0800-1700"), - (datetime.date(2024, 10, 9), "0800-1700"), - (datetime.date(2024, 10, 10), "0800-1700"), - (datetime.date(2024, 10, 11), "0800-1700"), - (datetime.date(2024, 10, 12), None), - (datetime.date(2024, 10, 13), None), - (datetime.date(2024, 10, 14), "0800-1700"), - (datetime.date(2024, 10, 15), "0800-1700"), - (datetime.date(2024, 10, 16), "0800-1700"), - (datetime.date(2024, 10, 17), "0800-1700"), - (datetime.date(2024, 10, 18), "0800-1700"), - (datetime.date(2024, 10, 19), None), - (datetime.date(2024, 10, 20), None), - (datetime.date(2024, 10, 21), "0800-1700"), - (datetime.date(2024, 10, 22), "0800-1700"), - (datetime.date(2024, 10, 23), "0800-1700"), - (datetime.date(2024, 10, 24), "0800-1700"), - (datetime.date(2024, 10, 25), "0800-1700"), - (datetime.date(2024, 10, 26), None), - (datetime.date(2024, 10, 27), None), - (datetime.date(2024, 10, 28), "0800-1700"), - (datetime.date(2024, 10, 29), "0800-1700"), - (datetime.date(2024, 10, 30), "0800-1700"), - (datetime.date(2024, 10, 31), "0800-1700"), - (datetime.date(2024, 11, 1), "0800-1700"), - (datetime.date(2024, 11, 2), None), - (datetime.date(2024, 11, 3), None), - (datetime.date(2024, 11, 4), "0800-1700"), - (datetime.date(2024, 11, 5), "0800-1700"), - (datetime.date(2024, 11, 6), "0800-1700"), - (datetime.date(2024, 11, 7), "0800-1700"), - (datetime.date(2024, 11, 8), "0800-1700"), - (datetime.date(2024, 11, 9), None), - (datetime.date(2024, 11, 10), None), - (datetime.date(2024, 11, 11), "0800-1700"), - (datetime.date(2024, 11, 12), "0800-1700"), - (datetime.date(2024, 11, 13), "0800-1700"), - (datetime.date(2024, 11, 14), "0800-1700"), - (datetime.date(2024, 11, 15), "0800-1700"), - (datetime.date(2024, 11, 16), None), - (datetime.date(2024, 11, 17), None), - (datetime.date(2024, 11, 18), "0800-1700"), - (datetime.date(2024, 11, 19), "0800-1700"), - (datetime.date(2024, 11, 20), "0800-1700"), - (datetime.date(2024, 11, 21), "0800-1700"), - (datetime.date(2024, 11, 22), "0800-1700"), - (datetime.date(2024, 11, 23), None), - (datetime.date(2024, 11, 24), None), - (datetime.date(2024, 11, 25), "0800-1700"), - (datetime.date(2024, 11, 26), "0800-1700"), - (datetime.date(2024, 11, 27), "0800-1700"), - (datetime.date(2024, 11, 28), None), - (datetime.date(2024, 11, 29), "0800-1300"), - (datetime.date(2024, 11, 30), None), - (datetime.date(2024, 12, 1), None), - (datetime.date(2024, 12, 2), "0800-1700"), - (datetime.date(2024, 12, 3), "0800-1700"), - (datetime.date(2024, 12, 4), "0800-1700"), - (datetime.date(2024, 12, 5), "0800-1700"), - (datetime.date(2024, 12, 6), "0800-1700"), - (datetime.date(2024, 12, 7), None), - (datetime.date(2024, 12, 8), None), - (datetime.date(2024, 12, 9), "0800-1700"), - (datetime.date(2024, 12, 10), "0800-1700"), - (datetime.date(2024, 12, 11), "0800-1700"), - (datetime.date(2024, 12, 12), "0800-1700"), - (datetime.date(2024, 12, 13), "0800-1700"), - (datetime.date(2024, 12, 14), None), - (datetime.date(2024, 12, 15), None), - (datetime.date(2024, 12, 16), "0800-1700"), - (datetime.date(2024, 12, 17), "0800-1700"), - (datetime.date(2024, 12, 18), "0800-1700"), - (datetime.date(2024, 12, 19), "0800-1700"), - (datetime.date(2024, 12, 20), "0800-1700"), - (datetime.date(2024, 12, 21), None), - (datetime.date(2024, 12, 22), None), - (datetime.date(2024, 12, 23), "0800-1700"), - (datetime.date(2024, 12, 24), "0800-1300"), - (datetime.date(2024, 12, 25), None), - (datetime.date(2024, 12, 26), "0800-1700"), - (datetime.date(2024, 12, 27), "0800-1700"), - (datetime.date(2024, 12, 28), None), - (datetime.date(2024, 12, 29), None), - (datetime.date(2024, 12, 30), "0800-1700"), - (datetime.date(2024, 12, 31), "0800-1700"), -] diff --git a/pythclient/market_schedule.py b/pythclient/market_schedule.py new file mode 100644 index 0000000..cda5a16 --- /dev/null +++ b/pythclient/market_schedule.py @@ -0,0 +1,209 @@ +from typing import List, Dict, Optional, Tuple +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +# A time is a string of the form HHMM and 2400 is used to represent midnight +Time = str + +# A time range is a tuple of two times, representing the start and end of a trading session. This +# range is *inclusive of the start time and exclusive of the end time*. +TimeRange = Tuple[Time, Time] + +# A daily schedule is a list of time ranges, representing the trading sessions for a given day +DailySchedule = List[TimeRange] + +class MarketSchedule: + def __init__(self, schedule_str: str): + """ + Parse a schedule string in Pyth format: "Timezone;WeeklySchedule;Holidays" + """ + + parts = schedule_str.split(';') + if len(parts) < 2: + raise ValueError("Schedule must contain at least timezone and weekly schedule") + + self.timezone = self._validate_timezone(parts[0]) + # Parse and validate weekly schedule - now returns parsed time ranges + self.weekly_schedule = self._parse_weekly_schedule(parts[1]) + self.holiday_schedule = self._parse_holidays(parts[2] if len(parts) > 2 else "") + + def _validate_timezone(self, timezone_str: str) -> ZoneInfo: + try: + return ZoneInfo(timezone_str) + except ZoneInfoNotFoundError: + raise ValueError(f"Invalid timezone: {timezone_str}") + + def _parse_weekly_schedule(self, schedule: str) -> List[DailySchedule]: + """Parse the weekly schedule (Mon-Sun) into daily schedules""" + days = schedule.split(',') + if len(days) != 7: + raise ValueError("Weekly schedule must contain exactly 7 days") + + weekly_schedule = [] + for day_schedule in days: + try: + weekly_schedule.append(self._parse_daily_schedule(day_schedule)) + except ValueError as e: + raise ValueError(f"Invalid schedule format: {day_schedule}") from e + + return weekly_schedule + + def _parse_holidays(self, holidays: str) -> Dict[str, DailySchedule]: + """Parse holiday overrides in format MMDD/Schedule""" + if not holidays: + return {} + + holiday_dict = {} + for holiday in holidays.split(','): + if not holiday: + continue + date_str, schedule = holiday.split('/') + holiday_dict[date_str] = self._parse_daily_schedule(schedule) + return holiday_dict + + def _parse_daily_schedule(self, schedule: str) -> DailySchedule: + """Parse time ranges in format HHMM-HHMM or HHMM-HHMM&HHMM-HHMM""" + if schedule == 'O': + return [('0000', '2400')] + elif schedule == 'C': + return [] + + ranges = [] + for time_range in schedule.split('&'): + start, end = time_range.split('-') + + + # Validate time format (HHMM) + if not (len(start) == 4 and len(end) == 4 and + start.isdigit() and end.isdigit() and + 0 <= int(start[:2]) <= 23 and 0 <= int(start[2:]) <= 59 and + ((0 <= int(end[:2]) <= 23 and 0 <= int(end[2:]) <= 59) or end == "2400")): + raise ValueError(f"Invalid time format in schedule: {start}-{end}") + + ranges.append((start, end)) + + # Sort ranges by start time + ranges.sort(key=lambda x: x[0]) + + return ranges + + def _get_time_ranges_for_date(self, dt: datetime) -> List[TimeRange]: + """Helper function to get time ranges for a specific date, checking holiday schedule first""" + date_str = dt.strftime("%m%d") + if date_str in self.holiday_schedule: + return self.holiday_schedule[date_str] + return self.weekly_schedule[dt.weekday()] + + def is_market_open(self, dt: datetime) -> bool: + """Check if the market is open at the given datetime""" + # Convert to market timezone + local_dt = dt.astimezone(self.timezone) + time_ranges = self._get_time_ranges_for_date(local_dt) + + if not time_ranges: + return False + + # Check current time against ranges + current_time = local_dt.strftime("%H%M") + return any(start <= current_time < end for start, end in time_ranges) + + def get_next_market_open(self, dt: datetime) -> Optional[datetime]: + """Get the next market open datetime after the given datetime. If the market + is open at the given datetime (even if just opens at the given time), + returns the next open datetime. + + If the market is always open, returns None. The returned datetime is in + the timezone of the input datetime.""" + input_tz = dt.tzinfo + current = dt.astimezone(self.timezone) + + # This flag is a invariant that indicates whether we're in the initial + # trading session of the given datetime as we move forward in time. + in_initial_trading_session = True + + # Look ahead up to 14 days + for _ in range(14): + time_ranges = self._get_time_ranges_for_date(current) + + current_time = current.strftime("%H%M") + + # Find the next open time after current_time + next_open = None + for start, end in time_ranges: + # If the end time is before the current time, skip + if end < current_time: + continue + + # If we're in the middle of a trading session, look for next session + # the complicated logic is to handle the distinction between + # a trading session that continues past midnight and one that doesn't + if start < current_time < end or (in_initial_trading_session and start <= current_time < end): + continue + + # Reaching here means we're no longer in a trading session + in_initial_trading_session = False + + # If this start time is after current time, this is the next open + if current_time < start: + next_open = start + break + + if next_open is not None: + # Found next opening time today + hour, minute = int(next_open[:2]), int(next_open[2:]) + result = current.replace(hour=hour, minute=minute, second=0, microsecond=0) + return result.astimezone(input_tz) # Convert back to input timezone + + # Move to next day at midnight + current = (current + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) + + # There is a potential edge case where the market immediately opens at midnight (when rolling over to the next day) + # In this case, the new current time is the open time. + if self.is_market_open(current) and not self.is_market_open(current - timedelta(minutes=1)): + return current.astimezone(input_tz) + + return None + + def get_next_market_close(self, dt: datetime) -> Optional[datetime]: + """Get the next market close datetime after the given datetime. If the + market just closes at the given datetime, returns the next close datetime. + + If the market is always open, returns None. The returned datetime is in + the timezone of the input datetime.""" + input_tz = dt.tzinfo + current = dt.astimezone(self.timezone) + + # Look ahead up to 14 days + for _ in range(14): + time_ranges = self._get_time_ranges_for_date(current) + + current_time = current.strftime("%H%M") + + # Find the next close time after current_time + next_close = None + for _, end in time_ranges: + # If the end time is before (or equal to) the current time, skip + if end <= current_time: + continue + + # If we're in a trading session or a new one starts and the end time + # doesn't roll over to the next day, this is the next close + if current_time < end and end < "2400": + next_close = end + break + + if next_close is not None: + # Found next closing time + hour, minute = int(next_close[:2]), int(next_close[2:]) + result = current.replace(hour=hour, minute=minute, second=0, microsecond=0) + return result.astimezone(input_tz) # Convert back to input timezone + + # Move to next day at midnight + current = (current + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) + + # There is a potential edge case where the market is not open at midnight (when rolling over to the next day) + # In this case, the new current time is the close time. + if not self.is_market_open(current) and self.is_market_open(current - timedelta(minutes=1)): + return current.astimezone(input_tz) + + return None diff --git a/pythclient/pythaccounts.py b/pythclient/pythaccounts.py index a035c43..0577f7e 100644 --- a/pythclient/pythaccounts.py +++ b/pythclient/pythaccounts.py @@ -7,6 +7,8 @@ from loguru import logger +from pythclient.market_schedule import MarketSchedule + from . import exceptions from .solana import SolanaPublicKey, SolanaPublicKeyOrStr, SolanaClient, SolanaAccount @@ -229,6 +231,13 @@ def symbol(self) -> str: Gets this account's symbol, or 'Unknown' if there is no 'symbol' attribute. """ return self.attrs.get("symbol", "Unknown") + + @property + def schedule(self) -> MarketSchedule: + """ + Gets the market schedule for this product. If the schedule is not set, returns an always open schedule. + """ + return MarketSchedule(self.attrs.get("schedule", "America/New_York;O,O,O,O,O,O,O;")) async def get_prices(self) -> Dict[PythPriceType, PythPriceAccount]: """ diff --git a/setup.py b/setup.py index 5c0e833..51f0adf 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='pythclient', - version='0.1.24', + version='0.2.0', packages=['pythclient'], author='Pyth Developers', author_email='contact@pyth.network', diff --git a/tests/test_calendar.py b/tests/test_calendar.py deleted file mode 100644 index 13789e8..0000000 --- a/tests/test_calendar.py +++ /dev/null @@ -1,489 +0,0 @@ -import datetime -from zoneinfo import ZoneInfo - -from pythclient.calendar import (get_next_market_close, get_next_market_open, - is_market_open) -from pythclient.calendar_full_intervals import (EQUITY_2024_INTERVALS, - FX_2024_INTERVALS, - METAL_2024_INTERVALS, - RATES_2024_INTERVALS) - -NY_TZ = ZoneInfo("America/New_York") -UTC_TZ = ZoneInfo("UTC") - -# Define constants for equity market -EQUITY_OPEN_WED_2023_6_21_12 = datetime.datetime(2023, 6, 21, 12, 0, 0, tzinfo=NY_TZ) -EQUITY_CLOSE_WED_2023_6_21_17 = datetime.datetime(2023, 6, 21, 17, 0, 0, tzinfo=NY_TZ) -EQUITY_CLOSE_SAT_2023_6_10_17 = datetime.datetime(2023, 6, 10, 17, 0, 0, tzinfo=NY_TZ) -EQUITY_HOLIDAY_MON_2023_6_19 = datetime.datetime(2023, 6, 19, tzinfo=NY_TZ) -EQUITY_HOLIDAY_NEXT_DAY_EARLY_CLOSE_OPEN_THU_2023_11_23_9_30 = datetime.datetime(2023, 11, 23, 9, 30, 0, tzinfo=NY_TZ) -EQUITY_HOLIDAY_NEXT_DAY_EARLY_CLOSE_CLOSE_THU_2023_11_23_13 = datetime.datetime(2023, 11, 23, 13, 0, 0, tzinfo=NY_TZ) -EQUITY_EARLY_CLOSE_OPEN_FRI_2023_11_24_11 = datetime.datetime(2023, 11, 24, 11, 0, 0, tzinfo=NY_TZ) -EQUITY_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14 = datetime.datetime(2023, 11, 24, 14, 0, 0, tzinfo=NY_TZ) - -# Define constants for fx & metal market -FX_METAL_OPEN_WED_2023_6_21_21 = datetime.datetime(2023, 6, 21, 21, 0, 0, tzinfo=NY_TZ) -FX_METAL_OPEN_WED_2023_6_21_23 = datetime.datetime(2023, 6, 21, 23, 0, 0, tzinfo=NY_TZ) -FX_METAL_CLOSE_SUN_2023_6_18_16 = datetime.datetime(2023, 6, 18, 16, 0, 0, tzinfo=NY_TZ) -FX_METAL_HOLIDAY_SUN_2023_1_1 = datetime.datetime(2023, 1, 1, tzinfo=NY_TZ) -FX_METAL_HOLIDAY_SUN_2023_12_24_17 = datetime.datetime(2023, 12, 24, 17, 0, 0, tzinfo=NY_TZ) -FX_METAL_HOLIDAY_SUN_2023_12_31_17 = datetime.datetime(2023, 12, 31, 17, 0, 0, tzinfo=NY_TZ) - -METAL_EARLY_HOLIDAY_MON_2024_1_15_13 = datetime.datetime(2024, 1, 15, 13, 0, 0, tzinfo=NY_TZ) -METAL_EARLY_HOLIDAY_MON_2024_1_15_17 = datetime.datetime(2024, 1, 15, 17, 0, 0, tzinfo=NY_TZ) -METAL_EARLY_HOLIDAY_MON_2024_1_15_18 = datetime.datetime(2024, 1, 15, 18, 0, 0, tzinfo=NY_TZ) - -# Define constants for rates market -RATES_OPEN_WED_2023_6_21_12 = datetime.datetime(2023, 6, 21, 8, 0, 0, tzinfo=NY_TZ) -RATES_CLOSE_WED_2023_6_21_17 = datetime.datetime(2023, 6, 21, 17, 0, 0, tzinfo=NY_TZ) -RATES_CLOSE_SAT_2023_6_10_17 = datetime.datetime(2023, 6, 10, 17, 0, 0, tzinfo=NY_TZ) -RATES_HOLIDAY_MON_2023_6_19 = datetime.datetime(2023, 6, 19, tzinfo=NY_TZ) -RATES_HOLIDAY_NEXT_DAY_EARLY_CLOSE_OPEN_THU_2023_11_23_8 = datetime.datetime(2023, 11, 23, 8, 0, 0, tzinfo=NY_TZ) -RATES_HOLIDAY_NEXT_DAY_EARLY_CLOSE_CLOSE_THU_2023_11_23_13 = datetime.datetime(2023, 11, 23, 13, 0, 0, tzinfo=NY_TZ) -RATES_EARLY_CLOSE_OPEN_FRI_2023_11_24_11 = datetime.datetime(2023, 11, 24, 11, 0, 0, tzinfo=NY_TZ) -RATES_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14 = datetime.datetime(2023, 11, 24, 14, 0, 0, tzinfo=NY_TZ) - -# Define constants for cryptocurrency market -CRYPTO_OPEN_WED_2023_6_21_12 = datetime.datetime(2023, 6, 21, 12, 0, 0, tzinfo=NY_TZ) -CRYPTO_OPEN_SUN_2023_6_18_12 = datetime.datetime(2023, 6, 18, 12, 0, 0, tzinfo=NY_TZ) - - -def format_datetime_to_unix_timestamp(dt: datetime.datetime): - # Convert the datetime object to a Unix timestamp in UTC - timestamp = dt.astimezone(UTC_TZ).timestamp() - unix_timestamp_utc = int(timestamp) - return unix_timestamp_utc - - -def test_is_market_open(): - # equity - # weekday, within equity market hours - assert is_market_open("equity", EQUITY_OPEN_WED_2023_6_21_12) == True - - # weekday, out of equity market hours - assert is_market_open("equity", EQUITY_CLOSE_WED_2023_6_21_17) == False - - # weekend, out of equity market hours - assert is_market_open("equity", EQUITY_CLOSE_SAT_2023_6_10_17) == False - - # weekday, NYSE holiday - assert is_market_open("equity", EQUITY_HOLIDAY_MON_2023_6_19) == False - - # weekday, NYSE early close holiday - assert is_market_open("equity", EQUITY_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) == True - assert is_market_open("equity", EQUITY_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) == False - - # fx & metal - # weekday, within fx & metal market hours - assert is_market_open("fx", FX_METAL_OPEN_WED_2023_6_21_21) == True - assert is_market_open("metal", FX_METAL_OPEN_WED_2023_6_21_21) == True - - # weekday, out of fx & metal market hours - assert is_market_open("fx", FX_METAL_CLOSE_SUN_2023_6_18_16) == False - assert is_market_open("metal", FX_METAL_CLOSE_SUN_2023_6_18_16) == False - - # fx & metal holiday - assert is_market_open("fx", FX_METAL_HOLIDAY_SUN_2023_1_1) == False - assert is_market_open("metal", FX_METAL_HOLIDAY_SUN_2023_1_1) == False - - # metal early holiday - assert is_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_13) == True - assert is_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_17) == False - assert is_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_18) == True - - - # fx & metal out of market hours on Sunday Dec 24 2023 after 10pm UTC - assert is_market_open("fx", FX_METAL_HOLIDAY_SUN_2023_12_24_17) == False - - # fx & metal out of market hours on Sunday Dec 31 2023 after 10pm UTC - assert is_market_open("fx", FX_METAL_HOLIDAY_SUN_2023_12_31_17) == False - - # rates - # weekday, within rates market hours - assert is_market_open("rates", RATES_OPEN_WED_2023_6_21_12) == True - - # weekday, out of rates market hours - assert is_market_open("rates", RATES_CLOSE_WED_2023_6_21_17) == False - - # weekend, out of rates market hours - assert is_market_open("rates", RATES_CLOSE_SAT_2023_6_10_17) == False - - # weekday, NYSE holiday - assert is_market_open("rates", RATES_HOLIDAY_MON_2023_6_19) == False - - # weekday, NYSE early close holiday - assert is_market_open("rates", RATES_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) == True - assert is_market_open("rates", RATES_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) == False - - # crypto - assert is_market_open("crypto", CRYPTO_OPEN_WED_2023_6_21_12) == True - assert is_market_open("crypto", CRYPTO_OPEN_SUN_2023_6_18_12) == True - - -def test_get_next_market_open(): - # equity within market hours - assert ( - get_next_market_open("equity", EQUITY_OPEN_WED_2023_6_21_12) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # equity out of market hours - assert ( - get_next_market_open("equity", EQUITY_CLOSE_WED_2023_6_21_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # equity weekend - assert ( - get_next_market_open("equity", EQUITY_CLOSE_SAT_2023_6_10_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 12, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # equity holiday - assert ( - get_next_market_open("equity", EQUITY_HOLIDAY_MON_2023_6_19) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 20, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # equity holiday next day early close holiday - assert ( - get_next_market_open("equity", EQUITY_HOLIDAY_NEXT_DAY_EARLY_CLOSE_OPEN_THU_2023_11_23_9_30) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # equity early close holiday - assert ( - get_next_market_open("equity", EQUITY_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 9, 30, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("equity", EQUITY_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 9, 30, 0, tzinfo=NY_TZ)) - ) - - # fx & metal within market hours (before 10pm UTC) - assert ( - get_next_market_open("fx", FX_METAL_OPEN_WED_2023_6_21_21) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", FX_METAL_OPEN_WED_2023_6_21_21) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - # fx & metal within market hours (after 10pm UTC) - assert ( - get_next_market_open("fx", FX_METAL_OPEN_WED_2023_6_21_23) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", FX_METAL_OPEN_WED_2023_6_21_23) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal out of market hours - assert ( - get_next_market_open("fx", FX_METAL_CLOSE_SUN_2023_6_18_16) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 18, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", FX_METAL_CLOSE_SUN_2023_6_18_16) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 18, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal out of market hours on Sunday Dec 24 2023 after 5pm ET - assert ( - get_next_market_open("fx", FX_METAL_HOLIDAY_SUN_2023_12_24_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 12, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", FX_METAL_HOLIDAY_SUN_2023_12_24_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 12, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal out of market hours on holiday Dec 25 2023 before 5pm ET - assert ( - get_next_market_open("fx", datetime.datetime(2023, 12, 25, 8, 15, 0, tzinfo=NY_TZ)) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 12, 25, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal holiday - assert ( - get_next_market_open("fx", FX_METAL_HOLIDAY_SUN_2023_1_1) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 1, 1, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", FX_METAL_HOLIDAY_SUN_2023_1_1) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 1, 1, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # metal early holiday - assert ( - get_next_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_13) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 15, 18, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 15, 18, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_18) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 21, 17, 0, 0, tzinfo=NY_TZ)) - ) - - - # rates within market hours - assert ( - get_next_market_open("rates", RATES_OPEN_WED_2023_6_21_12) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 8, 0, 0, tzinfo=NY_TZ)) - ) - - # rates out of market hours - assert ( - get_next_market_open("rates", RATES_CLOSE_WED_2023_6_21_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 8, 0, 0, tzinfo=NY_TZ)) - ) - - # rates weekend - assert ( - get_next_market_open("rates", RATES_CLOSE_SAT_2023_6_10_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 12, 8, 0, 0, tzinfo=NY_TZ)) - ) - - # rates holiday - assert ( - get_next_market_open("rates", RATES_HOLIDAY_MON_2023_6_19) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 20, 8, 0, 0, tzinfo=NY_TZ)) - ) - - # rates holiday next day early close holiday - assert ( - get_next_market_open("rates", RATES_HOLIDAY_NEXT_DAY_EARLY_CLOSE_OPEN_THU_2023_11_23_8) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 8, 0, 0, tzinfo=NY_TZ)) - ) - - # rates early close holiday - assert ( - get_next_market_open("rates", RATES_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 8, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_open("rates", RATES_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 8, 0, 0, tzinfo=NY_TZ)) - ) - - - # crypto - assert get_next_market_open("crypto", CRYPTO_OPEN_WED_2023_6_21_12) == None - assert get_next_market_open("crypto", CRYPTO_OPEN_SUN_2023_6_18_12) == None - - -def test_get_next_market_close(): - # equity within market hours - assert ( - get_next_market_close("equity", EQUITY_OPEN_WED_2023_6_21_12) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 21, 16, 0, 0, tzinfo=NY_TZ)) - ) - - # equity out of market hours - assert ( - get_next_market_close("equity", EQUITY_CLOSE_WED_2023_6_21_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 16, 0, 0, tzinfo=NY_TZ)) - ) - - # equity weekend - assert ( - get_next_market_close("equity", EQUITY_CLOSE_SAT_2023_6_10_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 12, 16, 0, 0, tzinfo=NY_TZ)) - ) - - # equity holiday - assert ( - get_next_market_close("equity", EQUITY_HOLIDAY_MON_2023_6_19) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 20, 16, 0, 0, tzinfo=NY_TZ)) - ) - - # equity holiday next day early close holiday - assert ( - get_next_market_close("equity", EQUITY_HOLIDAY_NEXT_DAY_EARLY_CLOSE_CLOSE_THU_2023_11_23_13) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 13, 0, 0, tzinfo=NY_TZ)) - ) - - # equity early close holiday - assert ( - get_next_market_close("equity", EQUITY_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 13, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("equity", EQUITY_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 16, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal within market hours (before 10pm UTC) - assert ( - get_next_market_close("fx", FX_METAL_OPEN_WED_2023_6_21_21) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", FX_METAL_OPEN_WED_2023_6_21_21) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal within market hours (after 10pm UTC) - assert ( - get_next_market_close("fx", FX_METAL_OPEN_WED_2023_6_21_23) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", FX_METAL_OPEN_WED_2023_6_21_23) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal within market hours on a friday (before 10pm UTC) - assert ( - get_next_market_close("fx", datetime.datetime(2023, 11, 10, 7, 0, 0, tzinfo=NY_TZ)) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 10, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal out of market hours - assert ( - get_next_market_close("fx", FX_METAL_CLOSE_SUN_2023_6_18_16) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", FX_METAL_CLOSE_SUN_2023_6_18_16) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 23, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # fx & metal holiday - assert ( - get_next_market_close("fx", FX_METAL_HOLIDAY_SUN_2023_1_1) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 1, 6, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", FX_METAL_HOLIDAY_SUN_2023_1_1) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 1, 6, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # metal early holiday - assert ( - get_next_market_close("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_13) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 15, 14, 30, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 19, 17, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("metal", METAL_EARLY_HOLIDAY_MON_2024_1_15_18) - == format_datetime_to_unix_timestamp(datetime.datetime(2024, 1, 19, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # rates within market hours - assert ( - get_next_market_close("rates", RATES_OPEN_WED_2023_6_21_12) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 21, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # rates out of market hours - assert ( - get_next_market_close("rates", RATES_CLOSE_WED_2023_6_21_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 22, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # rates weekend - assert ( - get_next_market_close("rates", RATES_CLOSE_SAT_2023_6_10_17) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 12, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # rates holiday - assert ( - get_next_market_close("rates", RATES_HOLIDAY_MON_2023_6_19) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 6, 20, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # rates holiday next day early close holiday - assert ( - get_next_market_close("rates", RATES_HOLIDAY_NEXT_DAY_EARLY_CLOSE_CLOSE_THU_2023_11_23_13) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 13, 0, 0, tzinfo=NY_TZ)) - ) - - # rates early close holiday - assert ( - get_next_market_close("rates", RATES_EARLY_CLOSE_OPEN_FRI_2023_11_24_11) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 24, 13, 0, 0, tzinfo=NY_TZ)) - ) - assert ( - get_next_market_close("rates", RATES_EARLY_CLOSE_CLOSE_FRI_2023_11_24_14) - == format_datetime_to_unix_timestamp(datetime.datetime(2023, 11, 27, 17, 0, 0, tzinfo=NY_TZ)) - ) - - # crypto - assert get_next_market_close("crypto", CRYPTO_OPEN_WED_2023_6_21_12) == None - assert get_next_market_close("crypto", CRYPTO_OPEN_SUN_2023_6_18_12) == None - - -def test_is_market_open_full(): - start_date = datetime.datetime(2024, 1, 1, tzinfo=NY_TZ) - end_date = datetime.datetime(2025, 1, 1, tzinfo=NY_TZ) - asset_types = ["equity", "fx", "metal", "rates", "crypto"] - - all_intervals = { - "equity": {}, - "fx": {}, - "metal": {}, - "rates": {}, - } - - data_sources = { - "equity": EQUITY_2024_INTERVALS, - "fx": FX_2024_INTERVALS, - "metal": METAL_2024_INTERVALS, - "rates": RATES_2024_INTERVALS, - } - - for asset_type, data in data_sources.items(): - for date, interval in data: - if date not in all_intervals[asset_type]: - all_intervals[asset_type][date] = [] - if interval != None: - all_intervals[asset_type][date].append(interval) - - current_date = start_date - while current_date < end_date: - for at in asset_types: - if at == "crypto": - continue - # Get the interval for the date - intervals = all_intervals[at].get(current_date.date()) - - if not intervals: - should_be_open = False - else: - should_be_open = is_time_in_intervals(current_date.time(), intervals) - pass - - # Check if the market is open - is_open = is_market_open(at, current_date) - - # Assert that the market is open if and only if it should be open - assert ( - is_open == should_be_open - ), f"Failed for asset type: {at}, date: {current_date}" - - # Add one minute to the current date - current_date += datetime.timedelta(minutes=1) - - -def is_time_in_intervals(current_time, intervals): - for interval in intervals: - start_time, end_time = [ - datetime.datetime.strptime(t, "%H%M").time() for t in interval.split("-") - ] - if start_time < end_time: - if start_time <= current_time < end_time: - return True - else: # Over midnight - if start_time <= current_time or current_time < end_time: - return True - return False diff --git a/tests/test_market_schedule.py b/tests/test_market_schedule.py new file mode 100644 index 0000000..888b481 --- /dev/null +++ b/tests/test_market_schedule.py @@ -0,0 +1,152 @@ +import pytest +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo +from pythclient.market_schedule import MarketSchedule + +# This fixtures are based on the schedule from the metadata of the assets taken at 2024-12-20 +FIXTURES = { + "amazonusd": "America/New_York;0930-1600,0930-1600,0930-1600,0930-1600,0930-1600,C,C;1224/0930-1300,1225/C,0101/C,0120/C,0217/C,0418/C,0526/C,0619/C", + "brent1musd": "America/New_York;0000-1800&2000-2400,0000-1800&2000-2400,0000-1800&2000-2400,0000-1800&2000-2400,0000-1800,C,1800-2400;1224/0000-1400&2000-2400,1225/C,0101/C,0418/C", + "btcusd": "America/New_York;O,O,O,O,O,O,O;", + "eurusd": "America/New_York;O,O,O,O,0000-1700,C,1700-2400;1224/0000-1700,1225/1700-2400,1231/0000-1700,0101/1700-2400", +} + +def test_market_open(): + # Test Amazon trading hours (regular NYSE hours) + schedule = MarketSchedule(FIXTURES["amazonusd"]) + + # Close at 9:29 ET + assert not schedule.is_market_open(datetime(2024, 3, 1, 9, 29, tzinfo=ZoneInfo("UTC"))) # 9:29 ET + # Open at 9:30 ET + assert schedule.is_market_open(datetime(2024, 3, 1, 14, 30, tzinfo=ZoneInfo("UTC"))) # 9:30 ET + + # Regular trading day + assert schedule.is_market_open(datetime(2024, 3, 1, 19, 30, tzinfo=ZoneInfo("UTC"))) # 14:30 ET + + # Open at 15:59 ET + assert schedule.is_market_open(datetime(2024, 3, 1, 20, 59, tzinfo=ZoneInfo("UTC"))) # 15:59 ET + # Close at 16:00 ET + assert not schedule.is_market_open(datetime(2024, 3, 1, 21, 0, tzinfo=ZoneInfo("UTC"))) # 16:00 ET + + # Weekend + assert not schedule.is_market_open(datetime(2024, 3, 2, 14, 30, tzinfo=ZoneInfo("UTC"))) # Saturday 9:30 ET + + # Holiday (Christmas) - market is closed + assert not schedule.is_market_open(datetime(2024, 12, 25, 14, 30, tzinfo=ZoneInfo("UTC"))) # Christmas 25/12/2024 9:30 ET + + # Holiday (Christmas Eve) - market has early close + assert not schedule.is_market_open(datetime(2024, 12, 24, 14, 29, tzinfo=ZoneInfo("UTC"))) # Christmas Eve 24/12/2024 9:29 ET + assert schedule.is_market_open(datetime(2024, 12, 24, 14, 30, tzinfo=ZoneInfo("UTC"))) # Christmas Eve 24/12/2024 9:30 ET + assert schedule.is_market_open(datetime(2024, 12, 24, 17, 59, tzinfo=ZoneInfo("UTC"))) # Christmas Eve 24/12/2024 12:59 ET + assert not schedule.is_market_open(datetime(2024, 12, 24, 18, 0, tzinfo=ZoneInfo("UTC"))) # Christmas Eve 24/12/2024 13:00 ET + +def test_next_market_open(): + schedule = MarketSchedule(FIXTURES["amazonusd"]) + + # Test next open from weekend + dt = datetime(2024, 3, 2, 12, 0, tzinfo=ZoneInfo("UTC")) # Saturday + next_open = schedule.get_next_market_open(dt) + assert next_open.strftime("%Y-%m-%d %H:%M") == "2024-03-04 14:30" # Monday 9:30 ET + + # Test next open from current open trading session (right at the start of the session) + dt = datetime(2024, 3, 1, 14, 30, tzinfo=ZoneInfo("UTC")) # Friday 9:30 ET + next_open = schedule.get_next_market_open(dt) + assert next_open.strftime("%Y-%m-%d %H:%M") == "2024-03-04 14:30" # Monday 9:30 ET + +def test_next_market_close(): + schedule = MarketSchedule(FIXTURES["amazonusd"]) + + # Test next close during trading hours + dt = datetime(2024, 3, 1, 14, 30, tzinfo=ZoneInfo("UTC")) # Friday 9:30 ET + next_close = schedule.get_next_market_close(dt) + assert next_close == datetime(2024, 3, 1, 21, 0, tzinfo=ZoneInfo("UTC")) + + # Test next close from the end of the trading session + dt = datetime(2024, 3, 1, 21, 0, tzinfo=ZoneInfo("UTC")) # Friday 16:00 ET + next_close = schedule.get_next_market_close(dt) + assert next_close == datetime(2024, 3, 4, 21, 0, tzinfo=ZoneInfo("UTC")) + + # Test next close from weekend + dt = datetime(2024, 3, 2, 12, 0, tzinfo=ZoneInfo("UTC")) # Saturday 8:00 ET + next_close = schedule.get_next_market_close(dt) + assert next_close == datetime(2024, 3, 4, 21, 0, tzinfo=ZoneInfo("UTC")) + +def test_complex_schedule_brent1musd(): + """Test Brent oil futures with multiple sessions per day""" + + schedule = MarketSchedule(FIXTURES["brent1musd"]) + + # Test during first session, 4 Dec 2024 (Wednesday) + assert schedule.is_market_open(datetime(2024, 12, 4, 0, 0, tzinfo=ZoneInfo("America/New_York"))) + assert schedule.is_market_open(datetime(2024, 12, 4, 12, 0, tzinfo=ZoneInfo("America/New_York"))) + + # Test during gap between sessions + assert not schedule.is_market_open(datetime(2024, 12, 4, 18, 0, tzinfo=ZoneInfo("America/New_York"))) + assert not schedule.is_market_open(datetime(2024, 12, 4, 19, 0, tzinfo=ZoneInfo("America/New_York"))) + + # Test during second session + assert schedule.is_market_open(datetime(2024, 12, 4, 20, 0, tzinfo=ZoneInfo("America/New_York"))) + assert schedule.is_market_open(datetime(2024, 12, 4, 23, 59, tzinfo=ZoneInfo("America/New_York"))) + + # Test next market close + next_close = schedule.get_next_market_close(datetime(2024, 12, 4, 23, 59, tzinfo=ZoneInfo("America/New_York"))) + assert next_close == datetime(2024, 12, 5, 18, 0, tzinfo=ZoneInfo("America/New_York")) + + # Test next market open + next_open = schedule.get_next_market_open(datetime(2024, 12, 4, 23, 59, tzinfo=ZoneInfo("America/New_York"))) + assert next_open == datetime(2024, 12, 5, 20, 0, tzinfo=ZoneInfo("America/New_York")) + +def test_always_open_schedule(): + """Test a schedule that is always open""" + + schedule = MarketSchedule(FIXTURES["btcusd"]) + assert schedule.is_market_open(datetime(2024, 12, 3, 23, 59, tzinfo=ZoneInfo("America/New_York"))) + + # Make sure next market open and next market close are None + assert schedule.get_next_market_open(datetime(2024, 12, 3, 23, 59, tzinfo=ZoneInfo("America/New_York"))) is None + assert schedule.get_next_market_close(datetime(2024, 12, 3, 23, 59, tzinfo=ZoneInfo("America/New_York"))) is None + +def test_invalid_schedules(): + # Test invalid timezone + with pytest.raises(ValueError): + MarketSchedule("Invalid/Timezone;0930-1600,0930-1600,0930-1600,0930-1600,0930-1600,C,C") + + # Test invalid number of days + with pytest.raises(ValueError): + MarketSchedule("America/New_York;0930-1600,0930-1600,0930-1600,0930-1600,0930-1600,C") + + # Test invalid time format + with pytest.raises(ValueError): + MarketSchedule("America/New_York;1600-2500,0930-1600,0930-1600,0930-1600,0930-1600,C,C") + + # Test invalid schedule format + with pytest.raises(ValueError): + MarketSchedule("America/New_York;0930-1600-1700,0930-1600,0930-1600,0930-1600,0930-1600,C,C") + + # Test invalid holiday schedule + with pytest.raises(ValueError): + MarketSchedule("America/New_York;0930-1600,0930-1600,0930-1600,0930-1600,0930-1600,C,C;1224/0930-2500") + +@pytest.mark.parametrize("asset", ["amazonusd", "brent1musd", "eurusd"]) +def test_walk_backwards_through_schedule(asset): + """Test walking backwards through the schedule to test the next market open and next market close""" + schedule = MarketSchedule(FIXTURES[asset]) + + current_time = datetime(2025, 6, 6, 0, 0, tzinfo=ZoneInfo("America/New_York")) + expected_next_market_open = None + expected_next_market_close = None + + while datetime(2024, 6, 6, 0, 0, tzinfo=ZoneInfo("America/New_York")) < current_time: + # update the expected next market open and next market close + if not schedule.is_market_open(current_time) and schedule.is_market_open(current_time + timedelta(minutes=1)): + expected_next_market_open = current_time + timedelta(minutes=1) + if schedule.is_market_open(current_time) and not schedule.is_market_open(current_time + timedelta(minutes=1)): + expected_next_market_close = current_time + timedelta(minutes=1) + + next_market_open = schedule.get_next_market_open(current_time) + next_market_close = schedule.get_next_market_close(current_time) + + assert not expected_next_market_open or next_market_open == expected_next_market_open + assert not expected_next_market_close or next_market_close == expected_next_market_close + + current_time -= timedelta(minutes=1)