Skip to content

Commit b97aad1

Browse files
authored
add calendar for pyth market hours (#31)
* add calendar for pyth market hours * add get_next_market_open
1 parent eb71516 commit b97aad1

File tree

2 files changed

+122
-2
lines changed

2 files changed

+122
-2
lines changed

pythclient/calendar.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import datetime
2+
3+
import pytz
4+
5+
TZ = pytz.timezone("America/New_York")
6+
7+
EQUITY_OPEN = datetime.time(9, 30, 0, tzinfo=TZ)
8+
EQUITY_CLOSE = datetime.time(16, 0, 0, tzinfo=TZ)
9+
EQUITY_EARLY_CLOSE = datetime.time(13, 0, 0, tzinfo=TZ)
10+
11+
# EQUITY_HOLIDAYS and EQUITY_EARLY_HOLIDAYS will need to be updated each year
12+
# From https://www.nyse.com/markets/hours-calendars
13+
EQUITY_HOLIDAYS = [
14+
datetime.datetime(2023, 1, 2, tzinfo=TZ).date(),
15+
datetime.datetime(2023, 1, 16, tzinfo=TZ).date(),
16+
datetime.datetime(2023, 2, 20, tzinfo=TZ).date(),
17+
datetime.datetime(2023, 4, 7, tzinfo=TZ).date(),
18+
datetime.datetime(2023, 5, 29, tzinfo=TZ).date(),
19+
datetime.datetime(2023, 6, 19, tzinfo=TZ).date(),
20+
datetime.datetime(2023, 7, 4, tzinfo=TZ).date(),
21+
datetime.datetime(2022, 9, 4, tzinfo=TZ).date(),
22+
datetime.datetime(2023, 11, 23, tzinfo=TZ).date(),
23+
datetime.datetime(2023, 12, 25, tzinfo=TZ).date(),
24+
]
25+
EQUITY_EARLY_HOLIDAYS = [
26+
datetime.datetime(2023, 7, 3, tzinfo=TZ).date(),
27+
datetime.datetime(2023, 11, 24, tzinfo=TZ).date(),
28+
]
29+
30+
FX_METAL_OPEN_CLOSE_TIME = datetime.time(17, 0, 0, tzinfo=TZ)
31+
32+
# FX_METAL_HOLIDAYS will need to be updated each year
33+
# From https://www.cboe.com/about/hours/fx/
34+
FX_METAL_HOLIDAYS = [
35+
datetime.datetime(2023, 1, 1, tzinfo=TZ).date(),
36+
datetime.datetime(2023, 12, 25, tzinfo=TZ).date(),
37+
]
38+
39+
40+
def is_market_open(asset_type: str, dt: datetime.datetime) -> bool:
41+
day, date, time = dt.weekday(), dt.date(), dt.time()
42+
43+
if asset_type == "equity":
44+
if date in EQUITY_HOLIDAYS or date in EQUITY_EARLY_HOLIDAYS:
45+
if (
46+
date in EQUITY_EARLY_HOLIDAYS
47+
and time >= EQUITY_OPEN
48+
and time <= EQUITY_EARLY_CLOSE
49+
):
50+
return True
51+
return False
52+
if day < 5 and time >= EQUITY_OPEN and time <= EQUITY_CLOSE:
53+
return True
54+
return False
55+
56+
if asset_type in ["fx", "metal"]:
57+
if date in FX_METAL_HOLIDAYS:
58+
return False
59+
# On Friday the market is closed after 5pm
60+
if day == 4 and time > FX_METAL_OPEN_CLOSE_TIME:
61+
return False
62+
# On Saturday the market is closed all the time
63+
if day == 5:
64+
return False
65+
# On Sunday the market is closed before 5pm
66+
if day == 6 and time < FX_METAL_OPEN_CLOSE_TIME:
67+
return False
68+
69+
return True
70+
71+
# all other markets (crypto)
72+
return True
73+
74+
75+
def get_next_market_open(asset_type: str, dt: datetime.datetime) -> str:
76+
time = dt.time()
77+
78+
if is_market_open(asset_type, dt):
79+
return dt.astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + "Z"
80+
81+
if asset_type == "equity":
82+
if time < EQUITY_OPEN:
83+
next_market_open = dt.replace(
84+
hour=EQUITY_OPEN.hour,
85+
minute=EQUITY_OPEN.minute,
86+
second=0,
87+
microsecond=0,
88+
)
89+
else:
90+
next_market_open = dt.replace(
91+
hour=EQUITY_OPEN.hour,
92+
minute=EQUITY_OPEN.minute,
93+
second=0,
94+
microsecond=0,
95+
)
96+
next_market_open += datetime.timedelta(days=1)
97+
elif asset_type in ["fx", "metal"]:
98+
if time < FX_METAL_OPEN_CLOSE_TIME:
99+
next_market_open = dt.replace(
100+
hour=FX_METAL_OPEN_CLOSE_TIME.hour,
101+
minute=FX_METAL_OPEN_CLOSE_TIME.minute,
102+
second=0,
103+
microsecond=0,
104+
)
105+
else:
106+
next_market_open = dt.replace(
107+
hour=FX_METAL_OPEN_CLOSE_TIME.hour,
108+
minute=FX_METAL_OPEN_CLOSE_TIME.minute,
109+
second=0,
110+
microsecond=0,
111+
)
112+
next_market_open += datetime.timedelta(days=1)
113+
else:
114+
next_market_open = dt.replace(hour=0, minute=0, second=0, microsecond=0)
115+
next_market_open += datetime.timedelta(days=1)
116+
117+
while not is_market_open(asset_type, next_market_open):
118+
next_market_open += datetime.timedelta(days=1)
119+
120+
return next_market_open.astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + "Z"

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from setuptools import setup
22

3-
requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'dnspython', 'flake8', 'loguru', 'typing-extensions']
3+
requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'dnspython', 'flake8', 'loguru', 'typing-extensions', 'pytz']
44

55
with open('README.md', 'r', encoding='utf-8') as fh:
66
long_description = fh.read()
77

88
setup(
99
name='pythclient',
10-
version='0.1.4',
10+
version='0.1.5',
1111
packages=['pythclient'],
1212
author='Pyth Developers',
1313
author_email='[email protected]',

0 commit comments

Comments
 (0)