Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rates calendar #43

Merged
merged 4 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 134 additions & 24 deletions pythclient/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

EQUITY_OPEN = datetime.time(9, 30, 0, tzinfo=NY_TZ)
EQUITY_CLOSE = datetime.time(16, 0, 0, tzinfo=NY_TZ)
EQUITY_EARLY_CLOSE = datetime.time(13, 0, 0, tzinfo=NY_TZ)

# EQUITY_HOLIDAYS and EQUITY_EARLY_HOLIDAYS will need to be updated each year
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
EQUITY_HOLIDAYS = [
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(),
Expand All @@ -22,7 +23,7 @@
datetime.datetime(2023, 11, 23, tzinfo=NY_TZ).date(),
datetime.datetime(2023, 12, 25, tzinfo=NY_TZ).date(),
]
EQUITY_EARLY_HOLIDAYS = [
NYSE_EARLY_HOLIDAYS = [
datetime.datetime(2023, 7, 3, tzinfo=NY_TZ).date(),
datetime.datetime(2023, 11, 24, tzinfo=NY_TZ).date(),
]
Expand All @@ -36,18 +37,21 @@
datetime.datetime(2023, 12, 25, 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 EQUITY_HOLIDAYS or date in EQUITY_EARLY_HOLIDAYS:
if date in NYSE_HOLIDAYS or date in NYSE_EARLY_HOLIDAYS:
if (
date in EQUITY_EARLY_HOLIDAYS
date in NYSE_EARLY_HOLIDAYS
and time >= EQUITY_OPEN
and time < EQUITY_EARLY_CLOSE
and time < NYSE_EARLY_CLOSE
):
return True
return False
Expand All @@ -70,6 +74,19 @@ def is_market_open(asset_type: str, dt: datetime.datetime) -> bool:

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

Expand Down Expand Up @@ -112,6 +129,22 @@ def get_next_market_open(asset_type: str, dt: datetime.datetime) -> int:
)
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

Expand All @@ -127,11 +160,11 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
time = dt.time()

if asset_type == "equity":
if dt.date() in EQUITY_EARLY_HOLIDAYS:
if time < EQUITY_EARLY_CLOSE:
if dt.date() in NYSE_EARLY_HOLIDAYS:
if time < NYSE_EARLY_CLOSE:
next_market_close = dt.replace(
hour=EQUITY_EARLY_CLOSE.hour,
minute=EQUITY_EARLY_CLOSE.minute,
hour=NYSE_EARLY_CLOSE.hour,
minute=NYSE_EARLY_CLOSE.minute,
second=0,
microsecond=0,
)
Expand All @@ -143,20 +176,35 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
microsecond=0,
)
next_market_close += datetime.timedelta(days=1)
elif dt.date() in EQUITY_HOLIDAYS:
next_market_open = get_next_market_open(
asset_type, dt + datetime.timedelta(days=1)
)
next_market_close = (
elif dt.date() in NYSE_HOLIDAYS:
next_market_open = get_next_market_open(asset_type, dt)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed this because there was a bug where this would return incorrect answer if the given date is a holiday and the next day is early close holiday

next_market_open_date = (
datetime.datetime.fromtimestamp(next_market_open)
.astimezone(NY_TZ)
.replace(
hour=EQUITY_CLOSE.hour,
minute=EQUITY_CLOSE.minute,
second=0,
microsecond=0,
)
.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,
Expand All @@ -167,9 +215,9 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
if time >= EQUITY_CLOSE:
next_market_close += datetime.timedelta(days=1)

# while next_market_close.date() is in EQUITY_HOLIDAYS or weekend, add 1 day
# while next_market_close.date() is in NYSE_HOLIDAYS or weekend, add 1 day
while (
next_market_close.date() in EQUITY_HOLIDAYS
next_market_close.date() in NYSE_HOLIDAYS
or next_market_close.weekday() >= 5
):
next_market_close += datetime.timedelta(days=1)
Expand All @@ -185,6 +233,68 @@ 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 == "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

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='pythclient',
version='0.1.15',
version='0.1.16',
packages=['pythclient'],
author='Pyth Developers',
author_email='[email protected]',
Expand Down
Loading