Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add order chains, add is_market_open_on util #171

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading