Skip to content

Commit

Permalink
add order chains, add is_market_open_on util (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
Graeme22 authored Oct 11, 2024
1 parent 2c903c6 commit a3ac177
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 6 deletions.
1 change: 1 addition & 0 deletions tastytrade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
API_URL = "https://api.tastyworks.com"
BACKTEST_URL = "https://backtester.vast.tastyworks.com"
CERT_URL = "https://api.cert.tastyworks.com"
VAST_URL = "https://vast.tastyworks.com"
VERSION = "9.0"

logger = logging.getLogger(__name__)
Expand Down
86 changes: 84 additions & 2 deletions tastytrade/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
from decimal import Decimal
from typing import Any, Dict, List, Literal, Optional, Union

import httpx
from pydantic import BaseModel, model_validator
from typing_extensions import Self

from tastytrade import VAST_URL
from tastytrade.order import (
InstrumentType,
NewComplexOrder,
NewOrder,
OrderAction,
OrderChain,
OrderStatus,
PlacedComplexOrder,
PlacedComplexOrderResponse,
Expand All @@ -26,6 +29,8 @@
validate_response,
)

TT_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"


class EmptyDict(BaseModel):
class Config:
Expand Down Expand Up @@ -1049,7 +1054,7 @@ async def a_get_net_liquidating_value_history(
params = {}
if start_time:
# format to Tastytrade DateTime format
params = {"start-time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ")}
params = {"start-time": start_time.strftime(TT_DATE_FMT)}
elif not time_back:
msg = "Either time_back or start_time must be specified."
raise TastytradeError(msg)
Expand Down Expand Up @@ -1083,7 +1088,7 @@ def get_net_liquidating_value_history(
params = {}
if start_time:
# format to Tastytrade DateTime format
params = {"start-time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ")}
params = {"start-time": start_time.strftime(TT_DATE_FMT)}
elif not time_back:
msg = "Either time_back or start_time must be specified."
raise TastytradeError(msg)
Expand Down Expand Up @@ -1641,3 +1646,80 @@ def replace_order(
),
)
return PlacedOrder(**data)

async def a_get_order_chains(
self,
session: Session,
symbol: str,
start_time: datetime,
end_time: datetime,
) -> List[OrderChain]:
"""
Get a list of order chains (open + rolls + close) for given symbol
over the given time frame, with total P/L, commissions, etc.
:param session: the session to use for the request.
:param symbol: the underlying symbol for the chains.
:param start_time: the beginning time of the query.
:param end_time: the ending time of the query.
"""
params = {
"account-numbers[]": self.account_number,
"underlying-symbols[]": symbol,
"start-at": start_time.strftime(TT_DATE_FMT),
"end-at": end_time.strftime(TT_DATE_FMT),
"defer-open-winner-loser-filtering-to-frontend": False,
"per-page": 250,
}
headers = {
"Authorization": session.session_token,
"Accept": "application/json",
"Content-Type": "application/json",
}
async with httpx.AsyncClient() as client:
response = await client.get(
f"{VAST_URL}/order-chains",
headers=headers,
params=params,
)
validate_response(response)
chains = response.json()["data"]["items"]
return [OrderChain(**i) for i in chains]

def get_order_chains(
self,
session: Session,
symbol: str,
start_time: datetime,
end_time: datetime,
) -> List[OrderChain]:
"""
Get a list of order chains (open + rolls + close) for given symbol
over the given time frame, with total P/L, commissions, etc.
:param session: the session to use for the request.
:param symbol: the underlying symbol for the chains.
:param start_time: the beginning time of the query.
:param end_time: the ending time of the query.
"""
params = {
"account-numbers[]": self.account_number,
"underlying-symbols[]": symbol,
"start-at": start_time.strftime(TT_DATE_FMT),
"end-at": end_time.strftime(TT_DATE_FMT),
"defer-open-winner-loser-filtering-to-frontend": False,
"per-page": 250,
}
headers = {
"Authorization": session.session_token,
"Accept": "application/json",
"Content-Type": "application/json",
}
response = httpx.get(
f"{VAST_URL}/order-chains",
headers=headers,
params=params,
)
validate_response(response)
chains = response.json()["data"]["items"]
return [OrderChain(**i) for i in chains]
6 changes: 3 additions & 3 deletions tastytrade/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,11 +528,11 @@ class OrderChain(TastytradeJsonDataclass):
"""

id: int
updated_at: datetime
created_at: datetime
account_number: str
description: str
underlying_symbol: str
computed_data: ComputedData
lite_nodes_sizes: int
lite_nodes: List[OrderChainNode]
lite_nodes_sizes: Optional[int] = None
updated_at: Optional[datetime] = None
created_at: Optional[datetime] = None
13 changes: 13 additions & 0 deletions tastytrade/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ def today_in_new_york() -> date:
return now_in_new_york().date()


def is_market_open_on(day: date = today_in_new_york()) -> bool:
"""
Returns whether the market was/is/will be open at ANY point
during the given day.
:param day: date to check
:return: whether the market opens on given day
"""
date_range = NYSE.valid_days(day, day)
return len(date_range) != 0


def get_third_friday(day: date = today_in_new_york()) -> date:
"""
Gets the monthly expiration associated with the month of the given date,
Expand Down
15 changes: 15 additions & 0 deletions tests/test_account.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from datetime import datetime
from decimal import Decimal
from time import sleep

Expand Down Expand Up @@ -148,6 +149,20 @@ async def test_get_live_orders_async(session, account):
await account.a_get_live_orders(session)


def test_get_order_chains(session, account):
start_time = datetime(2024, 1, 1, 0, 0, 0)
end_time = datetime.now()
account.get_order_chains(session, "F", start_time=start_time, end_time=end_time)


async def test_get_order_chains_async(session, account):
start_time = datetime(2024, 1, 1, 0, 0, 0)
end_time = datetime.now()
await account.a_get_order_chains(
session, "F", start_time=start_time, end_time=end_time
)


@fixture(scope="module")
def new_order(session):
symbol = Equity.get_equity(session, "F")
Expand Down
3 changes: 2 additions & 1 deletion tests/test_streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ async def test_dxlink_streamer(session):
async for _ in streamer.listen(Quote):
break
await streamer.unsubscribe_candle(subs[0], "1d")
await streamer.unsubscribe(Quote, subs)
await streamer.unsubscribe(Quote, [subs[0]])
await streamer.unsubscribe_all(Quote)

0 comments on commit a3ac177

Please sign in to comment.