Skip to content

Commit c862105

Browse files
JBlohmGraeme22
andauthored
Feature/market sessions (#215)
* Added market_sessions module * Added tests for market_sessions module * Fixed imports * Formating * Ammended: comments * Renamed instrument_collections / Added string enum for MarketState * Added: Docstring * Added: MarketState "Extended" * clean up code * Added: Docfile * Fixed: Typo * Adjusted: Doc test case * Fix: formating * update docs --------- Co-authored-by: Graeme Holliday <graeme.holliday@pm.me>
1 parent a5788cf commit c862105

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ A simple, reverse-engineered, sync/async SDK for Tastytrade built on their (now
3838
account-streamer
3939
data-streamer
4040
backtest
41+
market-sessions
4142
watchlists
4243

4344
.. toctree::

docs/market-sessions.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Market Sessions
2+
===============
3+
4+
A market time session object contains information about the current state of specific markets. It can be used to get the market opening and closing times and state.
5+
6+
The dataclass represents the current session and any nested 'next' or 'previous' session info.
7+
8+
The ``get_market_sessions`` function can be used to obtain information about the current session:
9+
10+
.. code-block:: python
11+
12+
from tastytrade.market_sessions import ExchangeType, get_market_sessions
13+
get_market_sessions(session, exchanges=[ExchangeType.NYSE])
14+
15+
>>> [MarketSession(close_at=None, close_at_ext=None, instrument_collection='Equity', open_at=None, start_at=None, next_session=MarketSessionSnapshot(close_at=datetime.datetime(2025, 2, 18, 21, 0, tzinfo=TzInfo(UTC)), close_at_ext=datetime.datetime(2025, 2, 19, 1, 0, tzinfo=TzInfo(UTC)), instrument_collection='Equity', open_at=datetime.datetime(2025, 2, 18, 14, 30, tzinfo=TzInfo(UTC)), session_date=datetime.date(2025, 2, 18), start_at=datetime.datetime(2025, 2, 18, 13, 15, tzinfo=TzInfo(UTC))), previous_session=MarketSessionSnapshot(close_at=datetime.datetime(2025, 2, 14, 21, 0, tzinfo=TzInfo(UTC)), close_at_ext=datetime.datetime(2025, 2, 15, 1, 0, tzinfo=TzInfo(UTC)), instrument_collection='Equity', open_at=datetime.datetime(2025, 2, 14, 14, 30, tzinfo=TzInfo(UTC)), session_date=datetime.date(2025, 2, 14), start_at=datetime.datetime(2025, 2, 14, 13, 15, tzinfo=TzInfo(UTC))), status=<MarketStatus.CLOSED: 'Closed'>)]
16+
17+
The ``get_market_holidays`` function can be used to obtain information about markets half days and holidays:
18+
19+
.. code-block:: python
20+
21+
from tastytrade.market_sessions import get_market_holidays
22+
calendar = Market.get_market_holidays(session)
23+
print(calendar.half_days)
24+
print(calendar.holidays)
25+
26+
>>> [datetime.date(2015, 12, 24), datetime.date(2016, 11, 25), datetime.date(2017, 7, 3), datetime.date(2017, 11, 24), datetime.date(2018, 7, 3), datetime.date(2018, 11, 23), datetime.date(2018, 12, 24), datetime.date(2019, 7, 3), datetime.date(2019, 11, 29), datetime.date(2019, 12, 24), datetime.date(2020, 11, 27), datetime.date(2020, 12, 24), datetime.date(2021, 11, 26), datetime.date(2022, 11, 25), datetime.date(2023, 7, 3), datetime.date(2023, 11, 24), datetime.date(2024, 7, 3), datetime.date(2024, 11, 29), datetime.date(2024, 12, 24), datetime.date(2025, 7, 3), datetime.date(2025, 11, 28), datetime.date(2025, 12, 24), datetime.date(2026, 11, 27), datetime.date(2026, 12, 24), datetime.date(2027, 7, 2), datetime.date(2027, 11, 26), datetime.date(2027, 12, 23), datetime.date(2028, 7, 3), datetime.date(2028, 11, 24), datetime.date(2028, 12, 22), datetime.date(2029, 7, 3)]
27+
>>> [datetime.date(2015, 12, 25), datetime.date(2016, 1, 1), datetime.date(2016, 1, 18), datetime.date(2016, 2, 15), datetime.date(2016, 3, 25), datetime.date(2016, 5, 30), datetime.date(2016, 7, 4), datetime.date(2016, 9, 5), datetime.date(2016, 11, 24), datetime.date(2016, 12, 26), datetime.date(2017, 1, 2), datetime.date(2017, 1, 16), datetime.date(2017, 2, 20), datetime.date(2017, 4, 14), datetime.date(2017, 5, 29), datetime.date(2017, 7, 4), datetime.date(2017, 9, 4), datetime.date(2017, 11, 23), datetime.date(2017, 12, 25), datetime.date(2018, 1, 1), datetime.date(2018, 1, 15), datetime.date(2018, 2, 19), datetime.date(2018, 3, 30), datetime.date(2018, 5, 28), datetime.date(2018, 7, 4), datetime.date(2018, 9, 3), datetime.date(2018, 11, 22), datetime.date(2018, 12, 5), datetime.date(2018, 12, 25), datetime.date(2019, 1, 1), datetime.date(2019, 1, 21), datetime.date(2019, 2, 18), datetime.date(2019, 4, 19), datetime.date(2019, 5, 27), datetime.date(2019, 7, 4), datetime.date(2019, 9, 2), datetime.date(2019, 11, 28), datetime.date(2019, 12, 25), datetime.date(2020, 1, 1), datetime.date(2020, 1, 20), datetime.date(2020, 2, 17), datetime.date(2020, 4, 10), datetime.date(2020, 5, 25), datetime.date(2020, 7, 3), datetime.date(2020, 9, 7), datetime.date(2020, 11, 26), datetime.date(2020, 12, 25), datetime.date(2021, 1, 1), datetime.date(2021, 1, 18), datetime.date(2021, 2, 15), datetime.date(2021, 4, 2), datetime.date(2021, 5, 31), datetime.date(2021, 7, 5), datetime.date(2021, 9, 6), datetime.date(2021, 11, 25), datetime.date(2021, 12, 24), datetime.date(2022, 1, 17), datetime.date(2022, 2, 21), datetime.date(2022, 4, 15), datetime.date(2022, 5, 30), datetime.date(2022, 6, 20), datetime.date(2022, 7, 4), datetime.date(2022, 9, 5), datetime.date(2022, 11, 24), datetime.date(2022, 12, 26), datetime.date(2023, 1, 2), datetime.date(2023, 1, 16), datetime.date(2023, 2, 20), datetime.date(2023, 4, 7), datetime.date(2023, 5, 29), datetime.date(2023, 6, 19), datetime.date(2023, 7, 4), datetime.date(2023, 9, 4), datetime.date(2023, 11, 23), datetime.date(2023, 12, 25), datetime.date(2024, 1, 1), datetime.date(2024, 1, 15), datetime.date(2024, 2, 19), datetime.date(2024, 3, 29), datetime.date(2024, 5, 27), datetime.date(2024, 6, 19), datetime.date(2024, 7, 4), datetime.date(2024, 9, 2), datetime.date(2024, 11, 28), datetime.date(2024, 12, 25), datetime.date(2025, 1, 1), datetime.date(2025, 1, 9), datetime.date(2025, 1, 20), datetime.date(2025, 2, 17), datetime.date(2025, 4, 18), datetime.date(2025, 5, 26), datetime.date(2025, 6, 19), datetime.date(2025, 7, 4), datetime.date(2025, 9, 1), datetime.date(2025, 11, 27), datetime.date(2025, 12, 25), datetime.date(2026, 1, 1), datetime.date(2026, 1, 19), datetime.date(2026, 2, 16), datetime.date(2026, 4, 3), datetime.date(2026, 5, 25), datetime.date(2026, 6, 19), datetime.date(2026, 7, 3), datetime.date(2026, 9, 7), datetime.date(2026, 11, 26), datetime.date(2026, 12, 25), datetime.date(2027, 1, 1), datetime.date(2027, 1, 18), datetime.date(2027, 2, 15), datetime.date(2027, 3, 26), datetime.date(2027, 5, 31), datetime.date(2027, 6, 18), datetime.date(2027, 7, 5), datetime.date(2027, 9, 6), datetime.date(2027, 11, 25), datetime.date(2027, 12, 24), datetime.date(2028, 1, 17), datetime.date(2028, 2, 21), datetime.date(2028, 4, 14), datetime.date(2028, 5, 29), datetime.date(2028, 6, 19), datetime.date(2028, 7, 4), datetime.date(2028, 9, 4), datetime.date(2028, 11, 23), datetime.date(2028, 12, 25), datetime.date(2029, 1, 1), datetime.date(2029, 1, 15), datetime.date(2029, 2, 19), datetime.date(2029, 3, 30), datetime.date(2029, 5, 28), datetime.date(2029, 6, 19), datetime.date(2029, 7, 4), datetime.date(2029, 9, 3)]
28+
29+
I case you only want to extract the market status, this is one way to do it:
30+
31+
.. code-block:: python
32+
33+
from tastytrade.market_sessions import ExchangeType, MarketStatus, get_market_sessions
34+
35+
market_sessions = get_market_sessions(session, exchanges=[ExchangeType.NYSE, ExchangeType.CME])
36+
print([ms.status != MarketStatus.CLOSED for ms in market_sessions])
37+
38+
>>> [False, False]

tastytrade/market_sessions.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from datetime import date, datetime
2+
from enum import Enum
3+
from typing import Optional
4+
5+
from pydantic import Field
6+
7+
from tastytrade.session import Session
8+
from tastytrade.utils import TastytradeJsonDataclass
9+
10+
11+
class ExchangeType(str, Enum):
12+
"""
13+
Contains the valid exchanges to get market sessions for.
14+
"""
15+
16+
CME = "CME"
17+
CFE = "CFE"
18+
NYSE = "Equity"
19+
SMALL = "Smalls"
20+
21+
22+
class MarketStatus(str, Enum):
23+
"""
24+
Contains the valid market status values.
25+
"""
26+
27+
OPEN = "Open"
28+
CLOSED = "Closed"
29+
PRE_MARKET = "Pre-market"
30+
EXTENDED = "Extended"
31+
32+
33+
class MarketSessionSnapshot(TastytradeJsonDataclass):
34+
"""
35+
Dataclass containing information about the upcoming or previous market session.
36+
"""
37+
38+
close_at: datetime
39+
close_at_ext: Optional[datetime] = None
40+
instrument_collection: str
41+
open_at: datetime
42+
session_date: date
43+
start_at: datetime
44+
45+
46+
class MarketSession(TastytradeJsonDataclass):
47+
"""
48+
Dataclass representing the current session as well as the next and previous sessions.
49+
"""
50+
51+
close_at: Optional[datetime] = None
52+
close_at_ext: Optional[datetime] = None
53+
instrument_collection: str
54+
open_at: Optional[datetime] = None
55+
start_at: Optional[datetime] = None
56+
next_session: Optional[MarketSessionSnapshot] = None
57+
previous_session: Optional[MarketSessionSnapshot] = None
58+
status: MarketStatus = Field(alias="state")
59+
60+
61+
class MarketCalendar(TastytradeJsonDataclass):
62+
"""
63+
Dataclass containing information about market holidays and shortened days.
64+
"""
65+
66+
half_days: list[date] = Field(alias="market-half-days")
67+
holidays: list[date] = Field(alias="market-holidays")
68+
69+
70+
async def a_get_market_sessions(
71+
session: Session, exchanges: list[ExchangeType]
72+
) -> list[MarketSession]:
73+
"""
74+
Retrieves a list of session timings for the given exchanges.
75+
76+
:param session: active user session to use
77+
:param exchanges: the list of exchanges to get market sessions for
78+
"""
79+
data = await session._a_get(
80+
"/market-time/sessions/current",
81+
params={"instrument-collections[]": [e.value for e in exchanges]},
82+
)
83+
return [MarketSession(**i) for i in data["items"]]
84+
85+
86+
def get_market_sessions(
87+
session: Session, exchanges: list[ExchangeType]
88+
) -> list[MarketSession]:
89+
"""
90+
Retrieves a list of session timings for the given exchanges.
91+
92+
:param session: active user session to use
93+
:param exchanges: the list of exchanges to get market sessions for
94+
"""
95+
data = session._get(
96+
"/market-time/sessions/current",
97+
params={"instrument-collections[]": [e.value for e in exchanges]},
98+
)
99+
return [MarketSession(**i) for i in data["items"]]
100+
101+
102+
async def a_get_market_holidays(session: Session) -> MarketCalendar:
103+
"""
104+
Retrieves market calendar for half days and holidays.
105+
106+
:param session: active user session to use
107+
"""
108+
data = await session._a_get("/market-time/equities/holidays")
109+
return MarketCalendar(**data)
110+
111+
112+
def get_market_holidays(session: Session) -> MarketCalendar:
113+
"""
114+
Retrieves market calendar for half days and holidays.
115+
116+
:param session: active user session to use
117+
"""
118+
data = session._get("/market-time/equities/holidays")
119+
return MarketCalendar(**data)

tests/test_market_sessions.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pytest import fixture
2+
3+
from tastytrade import Session
4+
from tastytrade.market_sessions import (
5+
ExchangeType,
6+
a_get_market_sessions,
7+
a_get_market_holidays,
8+
get_market_sessions,
9+
get_market_holidays,
10+
)
11+
12+
13+
@fixture
14+
def exchanges() -> list[ExchangeType]:
15+
return [ExchangeType.NYSE, ExchangeType.CME, ExchangeType.CFE, ExchangeType.SMALL]
16+
17+
18+
async def test_get_market_sessions_async(
19+
session: Session, exchanges: list[ExchangeType]
20+
):
21+
await a_get_market_sessions(session, exchanges=exchanges)
22+
23+
24+
async def test_get_market_holidays_async(session: Session):
25+
await a_get_market_holidays(session)
26+
27+
28+
def test_get_market_sessions(session: Session, exchanges: list[ExchangeType]):
29+
get_market_sessions(session, exchanges=exchanges)
30+
31+
32+
def test_get_market_holidays(session: Session):
33+
get_market_holidays(session)

0 commit comments

Comments
 (0)