diff --git a/pythclient/calendar.py b/pythclient/calendar.py index 29c5e94..5f2dcb5 100644 --- a/pythclient/calendar.py +++ b/pythclient/calendar.py @@ -41,17 +41,41 @@ datetime.datetime(2024, 12, 24, tzinfo=NY_TZ).date(), ] -FX_METAL_OPEN_CLOSE_TIME = datetime.time(17, 0, 0, tzinfo=NY_TZ) +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_METAL_HOLIDAYS = [ +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(), +] + RATES_OPEN = datetime.time(8, 0, 0, tzinfo=NY_TZ) RATES_CLOSE = datetime.time(17, 0, 0, tzinfo=NY_TZ) @@ -74,24 +98,48 @@ def is_market_open(asset_type: str, dt: datetime.datetime) -> bool: return True return False - if asset_type in ["fx", "metal"]: - if date in FX_METAL_HOLIDAYS and time < FX_METAL_OPEN_CLOSE_TIME: + 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_METAL_HOLIDAYS - ) and time >= FX_METAL_OPEN_CLOSE_TIME: + 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_METAL_OPEN_CLOSE_TIME: + 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_METAL_OPEN_CLOSE_TIME: + 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": @@ -132,25 +180,62 @@ def get_next_market_open(asset_type: str, dt: datetime.datetime) -> int: microsecond=0, ) next_market_open += datetime.timedelta(days=1) - elif asset_type in ["fx", "metal"]: - if (dt.weekday() == 6 and time < FX_METAL_OPEN_CLOSE_TIME) or ( - dt.date() in FX_METAL_HOLIDAYS and time < FX_METAL_OPEN_CLOSE_TIME + 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_METAL_OPEN_CLOSE_TIME.hour, - minute=FX_METAL_OPEN_CLOSE_TIME.minute, + hour=FX_OPEN_CLOSE_TIME.hour, + minute=FX_OPEN_CLOSE_TIME.minute, second=0, microsecond=0, ) else: next_market_open = dt.replace( - hour=FX_METAL_OPEN_CLOSE_TIME.hour, - minute=FX_METAL_OPEN_CLOSE_TIME.minute, + 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( @@ -244,10 +329,10 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int: ): next_market_close += datetime.timedelta(days=1) - elif asset_type in ["fx", "metal"]: + elif asset_type == "fx": next_market_close = dt.replace( - hour=FX_METAL_OPEN_CLOSE_TIME.hour, - minute=FX_METAL_OPEN_CLOSE_TIME.minute, + hour=FX_OPEN_CLOSE_TIME.hour, + minute=FX_OPEN_CLOSE_TIME.minute, second=0, microsecond=0, ) @@ -256,6 +341,36 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int: 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: diff --git a/pythclient/calendar_full_intervals.py b/pythclient/calendar_full_intervals.py index d439e7a..4bf50bf 100644 --- a/pythclient/calendar_full_intervals.py +++ b/pythclient/calendar_full_intervals.py @@ -369,7 +369,7 @@ (datetime.date(2024, 12, 31), "0930-1600"), ] -FX_METAL_2024_INTERVALS = [ +FX_2024_INTERVALS = [ (datetime.date(2023, 12, 31), None), (datetime.date(2024, 1, 1), "1700-0000"), (datetime.date(2024, 1, 2), "0000-0000"), @@ -739,6 +739,377 @@ (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-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"), diff --git a/setup.py b/setup.py index fa5c65c..247029a 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='pythclient', - version='0.1.20', + version='0.1.21', packages=['pythclient'], author='Pyth Developers', author_email='contact@pyth.network', diff --git a/tests/test_calendar.py b/tests/test_calendar.py index 802548e..13789e8 100644 --- a/tests/test_calendar.py +++ b/tests/test_calendar.py @@ -3,7 +3,10 @@ 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_METAL_2024_INTERVALS, RATES_2024_INTERVALS +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") @@ -26,6 +29,10 @@ 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) @@ -79,6 +86,12 @@ def test_is_market_open(): 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 @@ -203,6 +216,21 @@ def test_get_next_market_open(): == 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) @@ -336,6 +364,20 @@ def test_get_next_market_close(): == 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) @@ -386,36 +428,62 @@ def test_is_market_open_full(): end_date = datetime.datetime(2025, 1, 1, tzinfo=NY_TZ) asset_types = ["equity", "fx", "metal", "rates", "crypto"] - # Define the intervals - intervals = { - "equity": {date: interval for date, interval in EQUITY_2024_INTERVALS}, - "fx": {date: interval for date, interval in FX_METAL_2024_INTERVALS}, - "metal": {date: interval for date, interval in FX_METAL_2024_INTERVALS}, - "rates": {date: interval for date, interval in RATES_2024_INTERVALS} + 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 - interval = intervals[at].get(current_date.date()) + intervals = all_intervals[at].get(current_date.date()) - if interval is None: + if not intervals: should_be_open = False else: - start_time, end_time = [datetime.datetime.strptime(t, "%H%M").time() for t in interval.split('-')] - if start_time < end_time: - should_be_open = start_time <= current_date.time() < end_time - else: # Over midnight - should_be_open = start_time <= current_date.time() + 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}" - + 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