Skip to content

Commit a3ac177

Browse files
authored
add order chains, add is_market_open_on util (#171)
1 parent 2c903c6 commit a3ac177

File tree

6 files changed

+118
-6
lines changed

6 files changed

+118
-6
lines changed

tastytrade/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
API_URL = "https://api.tastyworks.com"
44
BACKTEST_URL = "https://backtester.vast.tastyworks.com"
55
CERT_URL = "https://api.cert.tastyworks.com"
6+
VAST_URL = "https://vast.tastyworks.com"
67
VERSION = "9.0"
78

89
logger = logging.getLogger(__name__)

tastytrade/account.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
from decimal import Decimal
33
from typing import Any, Dict, List, Literal, Optional, Union
44

5+
import httpx
56
from pydantic import BaseModel, model_validator
67
from typing_extensions import Self
78

9+
from tastytrade import VAST_URL
810
from tastytrade.order import (
911
InstrumentType,
1012
NewComplexOrder,
1113
NewOrder,
1214
OrderAction,
15+
OrderChain,
1316
OrderStatus,
1417
PlacedComplexOrder,
1518
PlacedComplexOrderResponse,
@@ -26,6 +29,8 @@
2629
validate_response,
2730
)
2831

32+
TT_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
33+
2934

3035
class EmptyDict(BaseModel):
3136
class Config:
@@ -1049,7 +1054,7 @@ async def a_get_net_liquidating_value_history(
10491054
params = {}
10501055
if start_time:
10511056
# format to Tastytrade DateTime format
1052-
params = {"start-time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ")}
1057+
params = {"start-time": start_time.strftime(TT_DATE_FMT)}
10531058
elif not time_back:
10541059
msg = "Either time_back or start_time must be specified."
10551060
raise TastytradeError(msg)
@@ -1083,7 +1088,7 @@ def get_net_liquidating_value_history(
10831088
params = {}
10841089
if start_time:
10851090
# format to Tastytrade DateTime format
1086-
params = {"start-time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ")}
1091+
params = {"start-time": start_time.strftime(TT_DATE_FMT)}
10871092
elif not time_back:
10881093
msg = "Either time_back or start_time must be specified."
10891094
raise TastytradeError(msg)
@@ -1641,3 +1646,80 @@ def replace_order(
16411646
),
16421647
)
16431648
return PlacedOrder(**data)
1649+
1650+
async def a_get_order_chains(
1651+
self,
1652+
session: Session,
1653+
symbol: str,
1654+
start_time: datetime,
1655+
end_time: datetime,
1656+
) -> List[OrderChain]:
1657+
"""
1658+
Get a list of order chains (open + rolls + close) for given symbol
1659+
over the given time frame, with total P/L, commissions, etc.
1660+
1661+
:param session: the session to use for the request.
1662+
:param symbol: the underlying symbol for the chains.
1663+
:param start_time: the beginning time of the query.
1664+
:param end_time: the ending time of the query.
1665+
"""
1666+
params = {
1667+
"account-numbers[]": self.account_number,
1668+
"underlying-symbols[]": symbol,
1669+
"start-at": start_time.strftime(TT_DATE_FMT),
1670+
"end-at": end_time.strftime(TT_DATE_FMT),
1671+
"defer-open-winner-loser-filtering-to-frontend": False,
1672+
"per-page": 250,
1673+
}
1674+
headers = {
1675+
"Authorization": session.session_token,
1676+
"Accept": "application/json",
1677+
"Content-Type": "application/json",
1678+
}
1679+
async with httpx.AsyncClient() as client:
1680+
response = await client.get(
1681+
f"{VAST_URL}/order-chains",
1682+
headers=headers,
1683+
params=params,
1684+
)
1685+
validate_response(response)
1686+
chains = response.json()["data"]["items"]
1687+
return [OrderChain(**i) for i in chains]
1688+
1689+
def get_order_chains(
1690+
self,
1691+
session: Session,
1692+
symbol: str,
1693+
start_time: datetime,
1694+
end_time: datetime,
1695+
) -> List[OrderChain]:
1696+
"""
1697+
Get a list of order chains (open + rolls + close) for given symbol
1698+
over the given time frame, with total P/L, commissions, etc.
1699+
1700+
:param session: the session to use for the request.
1701+
:param symbol: the underlying symbol for the chains.
1702+
:param start_time: the beginning time of the query.
1703+
:param end_time: the ending time of the query.
1704+
"""
1705+
params = {
1706+
"account-numbers[]": self.account_number,
1707+
"underlying-symbols[]": symbol,
1708+
"start-at": start_time.strftime(TT_DATE_FMT),
1709+
"end-at": end_time.strftime(TT_DATE_FMT),
1710+
"defer-open-winner-loser-filtering-to-frontend": False,
1711+
"per-page": 250,
1712+
}
1713+
headers = {
1714+
"Authorization": session.session_token,
1715+
"Accept": "application/json",
1716+
"Content-Type": "application/json",
1717+
}
1718+
response = httpx.get(
1719+
f"{VAST_URL}/order-chains",
1720+
headers=headers,
1721+
params=params,
1722+
)
1723+
validate_response(response)
1724+
chains = response.json()["data"]["items"]
1725+
return [OrderChain(**i) for i in chains]

tastytrade/order.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,11 +528,11 @@ class OrderChain(TastytradeJsonDataclass):
528528
"""
529529

530530
id: int
531-
updated_at: datetime
532-
created_at: datetime
533531
account_number: str
534532
description: str
535533
underlying_symbol: str
536534
computed_data: ComputedData
537-
lite_nodes_sizes: int
538535
lite_nodes: List[OrderChainNode]
536+
lite_nodes_sizes: Optional[int] = None
537+
updated_at: Optional[datetime] = None
538+
created_at: Optional[datetime] = None

tastytrade/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ def today_in_new_york() -> date:
4141
return now_in_new_york().date()
4242

4343

44+
def is_market_open_on(day: date = today_in_new_york()) -> bool:
45+
"""
46+
Returns whether the market was/is/will be open at ANY point
47+
during the given day.
48+
49+
:param day: date to check
50+
51+
:return: whether the market opens on given day
52+
"""
53+
date_range = NYSE.valid_days(day, day)
54+
return len(date_range) != 0
55+
56+
4457
def get_third_friday(day: date = today_in_new_york()) -> date:
4558
"""
4659
Gets the monthly expiration associated with the month of the given date,

tests/test_account.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from datetime import datetime
23
from decimal import Decimal
34
from time import sleep
45

@@ -148,6 +149,20 @@ async def test_get_live_orders_async(session, account):
148149
await account.a_get_live_orders(session)
149150

150151

152+
def test_get_order_chains(session, account):
153+
start_time = datetime(2024, 1, 1, 0, 0, 0)
154+
end_time = datetime.now()
155+
account.get_order_chains(session, "F", start_time=start_time, end_time=end_time)
156+
157+
158+
async def test_get_order_chains_async(session, account):
159+
start_time = datetime(2024, 1, 1, 0, 0, 0)
160+
end_time = datetime.now()
161+
await account.a_get_order_chains(
162+
session, "F", start_time=start_time, end_time=end_time
163+
)
164+
165+
151166
@fixture(scope="module")
152167
def new_order(session):
153168
symbol = Equity.get_equity(session, "F")

tests/test_streamer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ async def test_dxlink_streamer(session):
2727
async for _ in streamer.listen(Quote):
2828
break
2929
await streamer.unsubscribe_candle(subs[0], "1d")
30-
await streamer.unsubscribe(Quote, subs)
30+
await streamer.unsubscribe(Quote, [subs[0]])
31+
await streamer.unsubscribe_all(Quote)

0 commit comments

Comments
 (0)