Skip to content

Commit 17005ee

Browse files
authored
fix get_next_market_open/close (#33)
* fix get_next_market_open/close logic * bump * crypto return none for next market open * add unit tests and fix logic * update python dep to 3.9 * update pytest to 3.9
1 parent 944a24b commit 17005ee

File tree

4 files changed

+416
-21
lines changed

4 files changed

+416
-21
lines changed

.github/workflows/pytest.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: ["3.7", "3.8", "3.9"]
11+
python-version: ["3.9"]
1212

1313
steps:
1414
- uses: actions/checkout@v2

pythclient/calendar.py

+52-18
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ def is_market_open(asset_type: str, dt: datetime.datetime) -> bool:
4747
if (
4848
date in EQUITY_EARLY_HOLIDAYS
4949
and time >= EQUITY_OPEN
50-
and time <= EQUITY_EARLY_CLOSE
50+
and time < EQUITY_EARLY_CLOSE
5151
):
5252
return True
5353
return False
54-
if day < 5 and time >= EQUITY_OPEN and time <= EQUITY_CLOSE:
54+
if day < 5 and time >= EQUITY_OPEN and time < EQUITY_CLOSE:
5555
return True
5656
return False
5757

5858
if asset_type in ["fx", "metal"]:
5959
if date in FX_METAL_HOLIDAYS:
6060
return False
6161
# On Friday the market is closed after 5pm
62-
if day == 4 and time > FX_METAL_OPEN_CLOSE_TIME:
62+
if day == 4 and time >= FX_METAL_OPEN_CLOSE_TIME:
6363
return False
6464
# On Saturday the market is closed all the time
6565
if day == 5:
@@ -79,9 +79,6 @@ def get_next_market_open(asset_type: str, dt: datetime.datetime) -> str:
7979
dt = dt.astimezone(NY_TZ)
8080
time = dt.time()
8181

82-
if is_market_open(asset_type, dt):
83-
return dt.astimezone(UTC_TZ).strftime("%Y-%m-%dT%H:%M:%S") + "Z"
84-
8582
if asset_type == "equity":
8683
if time < EQUITY_OPEN:
8784
next_market_open = dt.replace(
@@ -113,30 +110,53 @@ def get_next_market_open(asset_type: str, dt: datetime.datetime) -> str:
113110
second=0,
114111
microsecond=0,
115112
)
116-
next_market_open += datetime.timedelta(days=1)
113+
while is_market_open(asset_type, next_market_open):
114+
next_market_open += datetime.timedelta(days=1)
115+
117116
else:
118-
next_market_open = dt.replace(hour=0, minute=0, second=0, microsecond=0)
119-
next_market_open += datetime.timedelta(days=1)
117+
return None
120118

121119
while not is_market_open(asset_type, next_market_open):
122120
next_market_open += datetime.timedelta(days=1)
123121

124122
return next_market_open.astimezone(UTC_TZ).strftime("%Y-%m-%dT%H:%M:%S") + "Z"
125123

124+
126125
def get_next_market_close(asset_type: str, dt: datetime.datetime) -> str:
127126
# make sure time is in NY timezone
128127
dt = dt.astimezone(NY_TZ)
129-
if not is_market_open(asset_type, dt):
130-
return dt.astimezone(UTC_TZ).strftime("%Y-%m-%dT%H:%M:%S") + "Z"
128+
time = dt.time()
131129

132130
if asset_type == "equity":
133131
if dt.date() in EQUITY_EARLY_HOLIDAYS:
134-
135-
next_market_close = dt.replace(
136-
hour=EQUITY_EARLY_CLOSE.hour,
137-
minute=EQUITY_EARLY_CLOSE.minute,
138-
second=0,
139-
microsecond=0,
132+
if time < EQUITY_EARLY_CLOSE:
133+
next_market_close = dt.replace(
134+
hour=EQUITY_EARLY_CLOSE.hour,
135+
minute=EQUITY_EARLY_CLOSE.minute,
136+
second=0,
137+
microsecond=0,
138+
)
139+
else:
140+
next_market_close = dt.replace(
141+
hour=EQUITY_CLOSE.hour,
142+
minute=EQUITY_CLOSE.minute,
143+
second=0,
144+
microsecond=0,
145+
)
146+
next_market_close += datetime.timedelta(days=1)
147+
elif dt.date() in EQUITY_HOLIDAYS:
148+
next_market_open = get_next_market_open(
149+
asset_type, dt + datetime.timedelta(days=1)
150+
)
151+
next_market_close = (
152+
datetime.datetime.fromisoformat(next_market_open.replace("Z", "+00:00"))
153+
.astimezone(NY_TZ)
154+
.replace(
155+
hour=EQUITY_CLOSE.hour,
156+
minute=EQUITY_CLOSE.minute,
157+
second=0,
158+
microsecond=0,
159+
)
140160
)
141161
else:
142162
next_market_close = dt.replace(
@@ -145,14 +165,28 @@ def get_next_market_close(asset_type: str, dt: datetime.datetime) -> str:
145165
second=0,
146166
microsecond=0,
147167
)
168+
if time >= EQUITY_CLOSE:
169+
next_market_close += datetime.timedelta(days=1)
170+
171+
# while next_market_close.date() is in EQUITY_HOLIDAYS or weekend, add 1 day
172+
while (
173+
next_market_close.date() in EQUITY_HOLIDAYS
174+
or next_market_close.weekday() >= 5
175+
):
176+
next_market_close += datetime.timedelta(days=1)
177+
148178
elif asset_type in ["fx", "metal"]:
149179
next_market_close = dt.replace(
150180
hour=FX_METAL_OPEN_CLOSE_TIME.hour,
151181
minute=FX_METAL_OPEN_CLOSE_TIME.minute,
152182
second=0,
153183
microsecond=0,
154184
)
155-
else: # crypto markets never close
185+
while not is_market_open(asset_type, next_market_close):
186+
next_market_close += datetime.timedelta(days=1)
187+
while is_market_open(asset_type, next_market_close):
188+
next_market_close += datetime.timedelta(days=1)
189+
else: # crypto markets never close
156190
return None
157191

158192
return next_market_close.astimezone(UTC_TZ).strftime("%Y-%m-%dT%H:%M:%S") + "Z"

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup(
99
name='pythclient',
10-
version='0.1.6',
10+
version='0.1.7',
1111
packages=['pythclient'],
1212
author='Pyth Developers',
1313
author_email='[email protected]',
@@ -28,5 +28,5 @@
2828
'testing': requirements + ['mock', 'pytest', 'pytest-cov', 'pytest-socket',
2929
'pytest-mock', 'pytest-asyncio'],
3030
},
31-
python_requires='>=3.7.0',
31+
python_requires='>=3.9.0',
3232
)

0 commit comments

Comments
 (0)