66
77EQUITY_OPEN = datetime .time (9 , 30 , 0 , tzinfo = NY_TZ )
88EQUITY_CLOSE = datetime .time (16 , 0 , 0 , tzinfo = NY_TZ )
9- EQUITY_EARLY_CLOSE = datetime .time (13 , 0 , 0 , tzinfo = NY_TZ )
109
11- # EQUITY_HOLIDAYS and EQUITY_EARLY_HOLIDAYS will need to be updated each year
10+ NYSE_EARLY_CLOSE = datetime .time (13 , 0 , 0 , tzinfo = NY_TZ )
11+
12+ # NYSE_HOLIDAYS and NYSE_EARLY_HOLIDAYS will need to be updated each year
1213# From https://www.nyse.com/markets/hours-calendars
13- EQUITY_HOLIDAYS = [
14+ NYSE_HOLIDAYS = [
1415 datetime .datetime (2023 , 1 , 2 , tzinfo = NY_TZ ).date (),
1516 datetime .datetime (2023 , 1 , 16 , tzinfo = NY_TZ ).date (),
1617 datetime .datetime (2023 , 2 , 20 , tzinfo = NY_TZ ).date (),
2223 datetime .datetime (2023 , 11 , 23 , tzinfo = NY_TZ ).date (),
2324 datetime .datetime (2023 , 12 , 25 , tzinfo = NY_TZ ).date (),
2425]
25- EQUITY_EARLY_HOLIDAYS = [
26+ NYSE_EARLY_HOLIDAYS = [
2627 datetime .datetime (2023 , 7 , 3 , tzinfo = NY_TZ ).date (),
2728 datetime .datetime (2023 , 11 , 24 , tzinfo = NY_TZ ).date (),
2829]
3637 datetime .datetime (2023 , 12 , 25 , tzinfo = NY_TZ ).date (),
3738]
3839
40+ RATES_OPEN = datetime .time (8 , 0 , 0 , tzinfo = NY_TZ )
41+ RATES_CLOSE = datetime .time (17 , 0 , 0 , tzinfo = NY_TZ )
42+
3943
4044def is_market_open (asset_type : str , dt : datetime .datetime ) -> bool :
4145 # make sure time is in NY timezone
4246 dt = dt .astimezone (NY_TZ )
4347 day , date , time = dt .weekday (), dt .date (), dt .time ()
4448
4549 if asset_type == "equity" :
46- if date in EQUITY_HOLIDAYS or date in EQUITY_EARLY_HOLIDAYS :
50+ if date in NYSE_HOLIDAYS or date in NYSE_EARLY_HOLIDAYS :
4751 if (
48- date in EQUITY_EARLY_HOLIDAYS
52+ date in NYSE_EARLY_HOLIDAYS
4953 and time >= EQUITY_OPEN
50- and time < EQUITY_EARLY_CLOSE
54+ and time < NYSE_EARLY_CLOSE
5155 ):
5256 return True
5357 return False
@@ -70,6 +74,19 @@ def is_market_open(asset_type: str, dt: datetime.datetime) -> bool:
7074
7175 return True
7276
77+ if asset_type == "rates" :
78+ if date in NYSE_HOLIDAYS or date in NYSE_EARLY_HOLIDAYS :
79+ if (
80+ date in NYSE_EARLY_HOLIDAYS
81+ and time >= RATES_OPEN
82+ and time < NYSE_EARLY_CLOSE
83+ ):
84+ return True
85+ return False
86+ if day < 5 and time >= RATES_OPEN and time < RATES_CLOSE :
87+ return True
88+ return False
89+
7390 # all other markets (crypto)
7491 return True
7592
@@ -112,6 +129,22 @@ def get_next_market_open(asset_type: str, dt: datetime.datetime) -> int:
112129 )
113130 while is_market_open (asset_type , next_market_open ):
114131 next_market_open += datetime .timedelta (days = 1 )
132+ elif asset_type == "rates" :
133+ if time < RATES_OPEN :
134+ next_market_open = dt .replace (
135+ hour = RATES_OPEN .hour ,
136+ minute = RATES_OPEN .minute ,
137+ second = 0 ,
138+ microsecond = 0 ,
139+ )
140+ else :
141+ next_market_open = dt .replace (
142+ hour = RATES_OPEN .hour ,
143+ minute = RATES_OPEN .minute ,
144+ second = 0 ,
145+ microsecond = 0 ,
146+ )
147+ next_market_open += datetime .timedelta (days = 1 )
115148 else :
116149 return None
117150
@@ -127,11 +160,11 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
127160 time = dt .time ()
128161
129162 if asset_type == "equity" :
130- if dt .date () in EQUITY_EARLY_HOLIDAYS :
131- if time < EQUITY_EARLY_CLOSE :
163+ if dt .date () in NYSE_EARLY_HOLIDAYS :
164+ if time < NYSE_EARLY_CLOSE :
132165 next_market_close = dt .replace (
133- hour = EQUITY_EARLY_CLOSE .hour ,
134- minute = EQUITY_EARLY_CLOSE .minute ,
166+ hour = NYSE_EARLY_CLOSE .hour ,
167+ minute = NYSE_EARLY_CLOSE .minute ,
135168 second = 0 ,
136169 microsecond = 0 ,
137170 )
@@ -143,20 +176,35 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
143176 microsecond = 0 ,
144177 )
145178 next_market_close += datetime .timedelta (days = 1 )
146- elif dt .date () in EQUITY_HOLIDAYS :
147- next_market_open = get_next_market_open (
148- asset_type , dt + datetime .timedelta (days = 1 )
149- )
150- next_market_close = (
179+ elif dt .date () in NYSE_HOLIDAYS :
180+ next_market_open = get_next_market_open (asset_type , dt )
181+ next_market_open_date = (
151182 datetime .datetime .fromtimestamp (next_market_open )
152183 .astimezone (NY_TZ )
153- .replace (
154- hour = EQUITY_CLOSE .hour ,
155- minute = EQUITY_CLOSE .minute ,
156- second = 0 ,
157- microsecond = 0 ,
158- )
184+ .date ()
159185 )
186+ if next_market_open_date in NYSE_EARLY_HOLIDAYS :
187+ next_market_close = (
188+ datetime .datetime .fromtimestamp (next_market_open )
189+ .astimezone (NY_TZ )
190+ .replace (
191+ hour = NYSE_EARLY_CLOSE .hour ,
192+ minute = NYSE_EARLY_CLOSE .minute ,
193+ second = 0 ,
194+ microsecond = 0 ,
195+ )
196+ )
197+ else :
198+ next_market_close = (
199+ datetime .datetime .fromtimestamp (next_market_open )
200+ .astimezone (NY_TZ )
201+ .replace (
202+ hour = EQUITY_CLOSE .hour ,
203+ minute = EQUITY_CLOSE .minute ,
204+ second = 0 ,
205+ microsecond = 0 ,
206+ )
207+ )
160208 else :
161209 next_market_close = dt .replace (
162210 hour = EQUITY_CLOSE .hour ,
@@ -167,9 +215,9 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
167215 if time >= EQUITY_CLOSE :
168216 next_market_close += datetime .timedelta (days = 1 )
169217
170- # while next_market_close.date() is in EQUITY_HOLIDAYS or weekend, add 1 day
218+ # while next_market_close.date() is in NYSE_HOLIDAYS or weekend, add 1 day
171219 while (
172- next_market_close .date () in EQUITY_HOLIDAYS
220+ next_market_close .date () in NYSE_HOLIDAYS
173221 or next_market_close .weekday () >= 5
174222 ):
175223 next_market_close += datetime .timedelta (days = 1 )
@@ -185,6 +233,68 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> int:
185233 next_market_close += datetime .timedelta (days = 1 )
186234 while is_market_open (asset_type , next_market_close ):
187235 next_market_close += datetime .timedelta (days = 1 )
236+ elif asset_type == "rates" :
237+ if dt .date () in NYSE_EARLY_HOLIDAYS :
238+ if time < NYSE_EARLY_CLOSE :
239+ next_market_close = dt .replace (
240+ hour = NYSE_EARLY_CLOSE .hour ,
241+ minute = NYSE_EARLY_CLOSE .minute ,
242+ second = 0 ,
243+ microsecond = 0 ,
244+ )
245+ else :
246+ next_market_close = dt .replace (
247+ hour = RATES_CLOSE .hour ,
248+ minute = RATES_CLOSE .minute ,
249+ second = 0 ,
250+ microsecond = 0 ,
251+ )
252+ next_market_close += datetime .timedelta (days = 1 )
253+ elif dt .date () in NYSE_HOLIDAYS :
254+ next_market_open = get_next_market_open (asset_type , dt )
255+ next_market_open_date = (
256+ datetime .datetime .fromtimestamp (next_market_open )
257+ .astimezone (NY_TZ )
258+ .date ()
259+ )
260+ if next_market_open_date in NYSE_EARLY_HOLIDAYS :
261+ next_market_close = (
262+ datetime .datetime .fromtimestamp (next_market_open )
263+ .astimezone (NY_TZ )
264+ .replace (
265+ hour = NYSE_EARLY_CLOSE .hour ,
266+ minute = NYSE_EARLY_CLOSE .minute ,
267+ second = 0 ,
268+ microsecond = 0 ,
269+ )
270+ )
271+ else :
272+ next_market_close = (
273+ datetime .datetime .fromtimestamp (next_market_open )
274+ .astimezone (NY_TZ )
275+ .replace (
276+ hour = RATES_CLOSE .hour ,
277+ minute = RATES_CLOSE .minute ,
278+ second = 0 ,
279+ microsecond = 0 ,
280+ )
281+ )
282+ else :
283+ next_market_close = dt .replace (
284+ hour = RATES_CLOSE .hour ,
285+ minute = RATES_CLOSE .minute ,
286+ second = 0 ,
287+ microsecond = 0 ,
288+ )
289+ if time >= RATES_CLOSE :
290+ next_market_close += datetime .timedelta (days = 1 )
291+
292+ # while next_market_close.date() is in NYSE_HOLIDAYS or weekend, add 1 day
293+ while (
294+ next_market_close .date () in NYSE_HOLIDAYS
295+ or next_market_close .weekday () >= 5
296+ ):
297+ next_market_close += datetime .timedelta (days = 1 )
188298 else : # crypto markets never close
189299 return None
190300
0 commit comments