tastytrade 11.0.4__tar.gz → 11.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tastytrade-11.0.4 → tastytrade-11.1.0}/PKG-INFO +1 -1
- {tastytrade-11.0.4 → tastytrade-11.1.0}/pyproject.toml +1 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/__init__.py +1 -2
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/account.py +0 -173
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/event.py +0 -2
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/instruments.py +1 -1
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/market_sessions.py +19 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/order.py +5 -124
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/session.py +8 -28
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/streamer.py +2 -9
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/utils.py +17 -24
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_account.py +111 -54
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_dxfeed.py +10 -17
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_market_sessions.py +10 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_utils.py +69 -1
- {tastytrade-11.0.4 → tastytrade-11.1.0}/uv.lock +48 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/CONTRIBUTING.md +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/FUNDING.yml +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/pull_request_template.md +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/workflows/python-app.yml +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/workflows/python-publish-test.yml +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.github/workflows/python-publish.yml +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.gitignore +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.python-version +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/.readthedocs.yaml +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/LICENSE +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/Makefile +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/README.md +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/Makefile +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/account-streamer.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/accounts.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/account.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/dxfeed.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/instruments.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/market-data.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/market-sessions.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/metrics.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/order.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/search.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/session.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/streamer.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/utils.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/api/watchlists.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/conf.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/data-streamer.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/img/netliq.png +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/index.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/installation.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/instruments.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/make.bat +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/market-data.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/market-sessions.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/orders.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/sessions.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/sync-async.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/docs/watchlists.rst +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/__init__.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/candle.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/greeks.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/profile.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/quote.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/summary.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/theoprice.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/timeandsale.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/trade.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/dxfeed/underlying.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/market_data.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/metrics.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/py.typed +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/search.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tastytrade/watchlists.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/__init__.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/conftest.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_instruments.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_market_data.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_metrics.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_search.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_session.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_streamer.py +0 -0
- {tastytrade-11.0.4 → tastytrade-11.1.0}/tests/test_watchlists.py +0 -0
|
@@ -3,8 +3,7 @@ import logging
|
|
|
3
3
|
API_URL = "https://api.tastyworks.com"
|
|
4
4
|
API_VERSION = "20251101"
|
|
5
5
|
CERT_URL = "https://api.cert.tastyworks.com"
|
|
6
|
-
|
|
7
|
-
VERSION = "11.0.4"
|
|
6
|
+
VERSION = "11.1.0"
|
|
8
7
|
|
|
9
8
|
__version__ = VERSION
|
|
10
9
|
version_str: str = f"tastyware/tastytrade:v{VERSION}"
|
|
@@ -2,17 +2,14 @@ from datetime import date, datetime
|
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
from typing import Any, Literal, cast, overload
|
|
4
4
|
|
|
5
|
-
import httpx
|
|
6
5
|
from pydantic import BaseModel, ConfigDict, model_validator
|
|
7
6
|
from typing_extensions import Self
|
|
8
7
|
|
|
9
|
-
from tastytrade import VAST_URL
|
|
10
8
|
from tastytrade.order import (
|
|
11
9
|
InstrumentType,
|
|
12
10
|
NewComplexOrder,
|
|
13
11
|
NewOrder,
|
|
14
12
|
OrderAction,
|
|
15
|
-
OrderChain,
|
|
16
13
|
OrderStatus,
|
|
17
14
|
PlacedComplexOrder,
|
|
18
15
|
PlacedComplexOrderResponse,
|
|
@@ -28,7 +25,6 @@ from tastytrade.utils import (
|
|
|
28
25
|
paginate,
|
|
29
26
|
set_sign_for,
|
|
30
27
|
today_in_new_york,
|
|
31
|
-
validate_response,
|
|
32
28
|
)
|
|
33
29
|
|
|
34
30
|
TT_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
|
@@ -296,23 +292,6 @@ class MarginReport(TastytradeData):
|
|
|
296
292
|
)
|
|
297
293
|
|
|
298
294
|
|
|
299
|
-
class MarginRequirement(TastytradeData):
|
|
300
|
-
"""
|
|
301
|
-
Dataclass containing general margin requirement information for a symbol.
|
|
302
|
-
"""
|
|
303
|
-
|
|
304
|
-
underlying_symbol: str
|
|
305
|
-
long_equity_initial: Decimal
|
|
306
|
-
short_equity_initial: Decimal
|
|
307
|
-
long_equity_maintenance: Decimal
|
|
308
|
-
short_equity_maintenance: Decimal
|
|
309
|
-
naked_option_standard: Decimal
|
|
310
|
-
naked_option_minimum: Decimal
|
|
311
|
-
naked_option_floor: Decimal
|
|
312
|
-
clearing_identifier: str | None = None
|
|
313
|
-
is_deleted: bool | None = None
|
|
314
|
-
|
|
315
|
-
|
|
316
295
|
class NetLiqOhlc(TastytradeData):
|
|
317
296
|
"""
|
|
318
297
|
Dataclass containing historical net liquidation data in OHLC format
|
|
@@ -334,23 +313,6 @@ class NetLiqOhlc(TastytradeData):
|
|
|
334
313
|
time: str
|
|
335
314
|
|
|
336
315
|
|
|
337
|
-
class PositionLimit(TastytradeData):
|
|
338
|
-
"""
|
|
339
|
-
Dataclass containing information about general account limits.
|
|
340
|
-
"""
|
|
341
|
-
|
|
342
|
-
account_number: str
|
|
343
|
-
equity_order_size: int
|
|
344
|
-
equity_option_order_size: int
|
|
345
|
-
future_order_size: int
|
|
346
|
-
future_option_order_size: int
|
|
347
|
-
underlying_opening_order_limit: int
|
|
348
|
-
equity_position_size: int
|
|
349
|
-
equity_option_position_size: int
|
|
350
|
-
future_position_size: int
|
|
351
|
-
future_option_position_size: int
|
|
352
|
-
|
|
353
|
-
|
|
354
316
|
class TradingStatus(TastytradeData):
|
|
355
317
|
"""
|
|
356
318
|
Dataclass containing information about an account's trading status, such
|
|
@@ -546,8 +508,6 @@ class Account(TastytradeData):
|
|
|
546
508
|
:param session: the session to use for the request.
|
|
547
509
|
:param account_number: the account ID to get.
|
|
548
510
|
:param include_closed: whether to include closed accounts in the results
|
|
549
|
-
|
|
550
|
-
:return: an account if an ID was provided; otherwise, a single account.
|
|
551
511
|
"""
|
|
552
512
|
if account_number:
|
|
553
513
|
data = session._get(f"/customers/me/accounts/{account_number}")
|
|
@@ -1045,58 +1005,6 @@ class Account(TastytradeData):
|
|
|
1045
1005
|
)
|
|
1046
1006
|
return [NetLiqOhlc(**i) for i in data["items"]]
|
|
1047
1007
|
|
|
1048
|
-
async def a_get_position_limit(self, session: Session) -> PositionLimit:
|
|
1049
|
-
"""
|
|
1050
|
-
Get the maximum order size information for the account.
|
|
1051
|
-
|
|
1052
|
-
:param session: the session to use for the request.
|
|
1053
|
-
"""
|
|
1054
|
-
data = await session._a_get(f"/accounts/{self.account_number}/position-limit")
|
|
1055
|
-
return PositionLimit(**data)
|
|
1056
|
-
|
|
1057
|
-
def get_position_limit(self, session: Session) -> PositionLimit:
|
|
1058
|
-
"""
|
|
1059
|
-
Get the maximum order size information for the account.
|
|
1060
|
-
|
|
1061
|
-
:param session: the session to use for the request.
|
|
1062
|
-
"""
|
|
1063
|
-
data = session._get(f"/accounts/{self.account_number}/position-limit")
|
|
1064
|
-
return PositionLimit(**data)
|
|
1065
|
-
|
|
1066
|
-
async def a_get_effective_margin_requirements(
|
|
1067
|
-
self, session: Session, symbol: str
|
|
1068
|
-
) -> MarginRequirement:
|
|
1069
|
-
"""
|
|
1070
|
-
Get the effective margin requirements for a given symbol.
|
|
1071
|
-
|
|
1072
|
-
:param session:
|
|
1073
|
-
the session to use for the request, can't be certification
|
|
1074
|
-
:param symbol: the symbol to get margin requirements for.
|
|
1075
|
-
"""
|
|
1076
|
-
if symbol:
|
|
1077
|
-
symbol = symbol.replace("/", "%2F")
|
|
1078
|
-
data = await session._a_get(
|
|
1079
|
-
f"/accounts/{self.account_number}/margin-requirements/{symbol}/effective"
|
|
1080
|
-
)
|
|
1081
|
-
return MarginRequirement(**data)
|
|
1082
|
-
|
|
1083
|
-
def get_effective_margin_requirements(
|
|
1084
|
-
self, session: Session, symbol: str
|
|
1085
|
-
) -> MarginRequirement:
|
|
1086
|
-
"""
|
|
1087
|
-
Get the effective margin requirements for a given symbol.
|
|
1088
|
-
|
|
1089
|
-
:param session:
|
|
1090
|
-
the session to use for the request, can't be certification
|
|
1091
|
-
:param symbol: the symbol to get margin requirements for.
|
|
1092
|
-
"""
|
|
1093
|
-
if symbol:
|
|
1094
|
-
symbol = symbol.replace("/", "%2F")
|
|
1095
|
-
data = session._get(
|
|
1096
|
-
f"/accounts/{self.account_number}/margin-requirements/{symbol}/effective"
|
|
1097
|
-
)
|
|
1098
|
-
return MarginRequirement(**data)
|
|
1099
|
-
|
|
1100
1008
|
async def a_get_margin_requirements(self, session: Session) -> MarginReport:
|
|
1101
1009
|
"""
|
|
1102
1010
|
Get the margin report for the account, with total margin requirements
|
|
@@ -1502,84 +1410,3 @@ class Account(TastytradeData):
|
|
|
1502
1410
|
),
|
|
1503
1411
|
)
|
|
1504
1412
|
return PlacedOrder(**data)
|
|
1505
|
-
|
|
1506
|
-
async def a_get_order_chains(
|
|
1507
|
-
self,
|
|
1508
|
-
session: Session,
|
|
1509
|
-
symbol: str,
|
|
1510
|
-
start_time: datetime,
|
|
1511
|
-
end_time: datetime,
|
|
1512
|
-
) -> list[OrderChain]:
|
|
1513
|
-
"""
|
|
1514
|
-
Get a list of order chains (open + rolls + close) for given symbol
|
|
1515
|
-
over the given time frame, with total P/L, commissions, etc.
|
|
1516
|
-
|
|
1517
|
-
Not supported for OAuth sessions--write Tasty to get this added!
|
|
1518
|
-
|
|
1519
|
-
:param session: the session to use for the request.
|
|
1520
|
-
:param symbol: the underlying symbol for the chains.
|
|
1521
|
-
:param start_time: the beginning time of the query.
|
|
1522
|
-
:param end_time: the ending time of the query.
|
|
1523
|
-
"""
|
|
1524
|
-
params = {
|
|
1525
|
-
"account-numbers[]": self.account_number,
|
|
1526
|
-
"underlying-symbols[]": symbol,
|
|
1527
|
-
"start-at": start_time.strftime(TT_DATE_FMT),
|
|
1528
|
-
"end-at": end_time.strftime(TT_DATE_FMT),
|
|
1529
|
-
"defer-open-winner-loser-filtering-to-frontend": False,
|
|
1530
|
-
"per-page": 250,
|
|
1531
|
-
}
|
|
1532
|
-
headers = {
|
|
1533
|
-
"Authorization": session.session_token,
|
|
1534
|
-
"Accept": "application/json",
|
|
1535
|
-
"Content-Type": "application/json",
|
|
1536
|
-
}
|
|
1537
|
-
async with httpx.AsyncClient() as client:
|
|
1538
|
-
response = await client.get(
|
|
1539
|
-
f"{VAST_URL}/order-chains",
|
|
1540
|
-
headers=headers,
|
|
1541
|
-
params=params, # type: ignore[arg-type]
|
|
1542
|
-
)
|
|
1543
|
-
validate_response(response)
|
|
1544
|
-
chains = response.json()["data"]["items"]
|
|
1545
|
-
return [OrderChain(**i) for i in chains]
|
|
1546
|
-
|
|
1547
|
-
def get_order_chains(
|
|
1548
|
-
self,
|
|
1549
|
-
session: Session,
|
|
1550
|
-
symbol: str,
|
|
1551
|
-
start_time: datetime,
|
|
1552
|
-
end_time: datetime,
|
|
1553
|
-
) -> list[OrderChain]:
|
|
1554
|
-
"""
|
|
1555
|
-
Get a list of order chains (open + rolls + close) for given symbol
|
|
1556
|
-
over the given time frame, with total P/L, commissions, etc.
|
|
1557
|
-
|
|
1558
|
-
Not supported for OAuth sessions--write Tasty to get this added!
|
|
1559
|
-
|
|
1560
|
-
:param session: the session to use for the request.
|
|
1561
|
-
:param symbol: the underlying symbol for the chains.
|
|
1562
|
-
:param start_time: the beginning time of the query.
|
|
1563
|
-
:param end_time: the ending time of the query.
|
|
1564
|
-
"""
|
|
1565
|
-
params = {
|
|
1566
|
-
"account-numbers[]": self.account_number,
|
|
1567
|
-
"underlying-symbols[]": symbol,
|
|
1568
|
-
"start-at": start_time.strftime(TT_DATE_FMT),
|
|
1569
|
-
"end-at": end_time.strftime(TT_DATE_FMT),
|
|
1570
|
-
"defer-open-winner-loser-filtering-to-frontend": False,
|
|
1571
|
-
"per-page": 250,
|
|
1572
|
-
}
|
|
1573
|
-
headers = {
|
|
1574
|
-
"Authorization": session.session_token,
|
|
1575
|
-
"Accept": "application/json",
|
|
1576
|
-
"Content-Type": "application/json",
|
|
1577
|
-
}
|
|
1578
|
-
response = httpx.get(
|
|
1579
|
-
f"{VAST_URL}/order-chains",
|
|
1580
|
-
headers=headers,
|
|
1581
|
-
params=params, # type: ignore[arg-type]
|
|
1582
|
-
)
|
|
1583
|
-
validate_response(response)
|
|
1584
|
-
chains = response.json()["data"]["items"]
|
|
1585
|
-
return [OrderChain(**i) for i in chains]
|
|
@@ -490,8 +490,8 @@ class Option(TradeableTastytradeData):
|
|
|
490
490
|
stops_trading_at: datetime
|
|
491
491
|
market_time_instrument_collection: str
|
|
492
492
|
days_to_expiration: int
|
|
493
|
-
expires_at: datetime
|
|
494
493
|
is_closing_only: bool
|
|
494
|
+
expires_at: datetime | None = None
|
|
495
495
|
streamer_symbol: str = ""
|
|
496
496
|
listed_market: str | None = None
|
|
497
497
|
halted_at: datetime | None = None
|
|
@@ -118,6 +118,25 @@ def get_market_holidays(session: Session) -> MarketCalendar:
|
|
|
118
118
|
return MarketCalendar(**data)
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
async def a_get_futures_holidays(
|
|
122
|
+
session: Session, exchange: ExchangeType
|
|
123
|
+
) -> MarketCalendar:
|
|
124
|
+
"""
|
|
125
|
+
Retrieves market calendar for half days and holidays for a futures exchange.
|
|
126
|
+
|
|
127
|
+
:param session: active user session to use
|
|
128
|
+
:param exchange: exchange to fetch calendar for
|
|
129
|
+
"""
|
|
130
|
+
data = await session._a_get(f"/market-time/futures/holidays/{exchange.value}")
|
|
131
|
+
return MarketCalendar(**data)
|
|
132
|
+
|
|
133
|
+
|
|
121
134
|
def get_futures_holidays(session: Session, exchange: ExchangeType) -> MarketCalendar:
|
|
135
|
+
"""
|
|
136
|
+
Retrieves market calendar for half days and holidays for a futures exchange.
|
|
137
|
+
|
|
138
|
+
:param session: active user session to use
|
|
139
|
+
:param exchange: exchange to fetch calendar for
|
|
140
|
+
"""
|
|
122
141
|
data = session._get(f"/market-time/futures/holidays/{exchange.value}")
|
|
123
142
|
return MarketCalendar(**data)
|
|
@@ -3,7 +3,7 @@ from decimal import Decimal
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from pydantic import computed_field, field_serializer, model_validator
|
|
6
|
+
from pydantic import ConfigDict, computed_field, field_serializer, model_validator
|
|
7
7
|
|
|
8
8
|
from tastytrade import version_str
|
|
9
9
|
from tastytrade.utils import (
|
|
@@ -133,7 +133,7 @@ class Leg(TastytradeData):
|
|
|
133
133
|
instrument_type: InstrumentType
|
|
134
134
|
symbol: str
|
|
135
135
|
action: OrderAction
|
|
136
|
-
quantity: Decimal | None = None
|
|
136
|
+
quantity: Decimal | int | None = None
|
|
137
137
|
remaining_quantity: Decimal | None = None
|
|
138
138
|
fills: list[FillInfo] | None = None
|
|
139
139
|
|
|
@@ -149,15 +149,13 @@ class TradeableTastytradeData(TastytradeData):
|
|
|
149
149
|
instrument_type: InstrumentType
|
|
150
150
|
symbol: str
|
|
151
151
|
|
|
152
|
-
def build_leg(self, quantity: Decimal | None, action: OrderAction) -> Leg:
|
|
152
|
+
def build_leg(self, quantity: Decimal | int | None, action: OrderAction) -> Leg:
|
|
153
153
|
"""
|
|
154
154
|
Builds an order :class:`Leg` from the dataclass.
|
|
155
155
|
|
|
156
156
|
:param quantity:
|
|
157
157
|
the quantity of the symbol to trade, set this as `None` for notional orders
|
|
158
158
|
:param action: :class:`OrderAction` to perform, e.g. BUY_TO_OPEN
|
|
159
|
-
|
|
160
|
-
:return: a :class:`Leg` object
|
|
161
159
|
"""
|
|
162
160
|
return Leg(
|
|
163
161
|
instrument_type=self.instrument_type,
|
|
@@ -239,6 +237,8 @@ class NewOrder(TastytradeData):
|
|
|
239
237
|
modifying existing orders.
|
|
240
238
|
"""
|
|
241
239
|
|
|
240
|
+
model_config = ConfigDict(extra="allow")
|
|
241
|
+
|
|
242
242
|
time_in_force: OrderTimeInForce
|
|
243
243
|
order_type: OrderType
|
|
244
244
|
source: str = version_str
|
|
@@ -437,122 +437,3 @@ class PlacedOrderResponse(TastytradeData):
|
|
|
437
437
|
fee_calculation: FeeCalculation | None = None
|
|
438
438
|
warnings: list[Message] | None = None
|
|
439
439
|
errors: list[Message] | None = None
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
class OrderChainEntry(TastytradeData):
|
|
443
|
-
"""
|
|
444
|
-
Dataclass containing information about a single order in an order chain.
|
|
445
|
-
"""
|
|
446
|
-
|
|
447
|
-
symbol: str
|
|
448
|
-
instrument_type: InstrumentType
|
|
449
|
-
quantity: str
|
|
450
|
-
quantity_type: str
|
|
451
|
-
quantity_numeric: Decimal
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
class OrderChainLeg(TastytradeData):
|
|
455
|
-
"""
|
|
456
|
-
Dataclass containing information about a single leg in an order
|
|
457
|
-
from an order chain.
|
|
458
|
-
"""
|
|
459
|
-
|
|
460
|
-
symbol: str
|
|
461
|
-
instrument_type: InstrumentType
|
|
462
|
-
action: OrderAction
|
|
463
|
-
fill_quantity: Decimal
|
|
464
|
-
order_quantity: Decimal
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
class OrderChainNode(TastytradeData):
|
|
468
|
-
"""
|
|
469
|
-
Dataclass containing information about a single node in an order chain.
|
|
470
|
-
"""
|
|
471
|
-
|
|
472
|
-
node_type: str
|
|
473
|
-
id: str
|
|
474
|
-
description: str
|
|
475
|
-
occurred_at: datetime | None = None
|
|
476
|
-
total_fees: Decimal | None = None
|
|
477
|
-
total_fill_cost: Decimal | None = None
|
|
478
|
-
gcd_quantity: Decimal | None = None
|
|
479
|
-
fill_cost_per_quantity: Decimal | None = None
|
|
480
|
-
order_fill_count: int | None = None
|
|
481
|
-
roll: bool | None = None
|
|
482
|
-
legs: list[OrderChainLeg] | None = None
|
|
483
|
-
entries: list[OrderChainEntry] | None = None
|
|
484
|
-
|
|
485
|
-
@model_validator(mode="before")
|
|
486
|
-
@classmethod
|
|
487
|
-
def validate_price_effects(cls, data: Any) -> Any:
|
|
488
|
-
return set_sign_for(
|
|
489
|
-
data,
|
|
490
|
-
[
|
|
491
|
-
"total_fees",
|
|
492
|
-
"total_fill_cost",
|
|
493
|
-
"fill_cost_per_quantity",
|
|
494
|
-
],
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
class ComputedData(TastytradeData):
|
|
499
|
-
"""
|
|
500
|
-
Dataclass containing computed data about an order chain.
|
|
501
|
-
"""
|
|
502
|
-
|
|
503
|
-
open: bool
|
|
504
|
-
updated_at: datetime
|
|
505
|
-
total_fees: Decimal
|
|
506
|
-
total_commissions: Decimal
|
|
507
|
-
realized_gain: Decimal
|
|
508
|
-
realized_gain_with_fees: Decimal
|
|
509
|
-
winner_realized_and_closed: bool
|
|
510
|
-
winner_realized: bool
|
|
511
|
-
winner_realized_with_fees: bool
|
|
512
|
-
roll_count: int
|
|
513
|
-
opened_at: datetime
|
|
514
|
-
last_occurred_at: datetime
|
|
515
|
-
started_at_days_to_expiration: int
|
|
516
|
-
duration: int
|
|
517
|
-
total_opening_cost: Decimal
|
|
518
|
-
total_closing_cost: Decimal
|
|
519
|
-
total_cost: Decimal
|
|
520
|
-
gcd_open_quantity: Decimal
|
|
521
|
-
fees_missing: bool
|
|
522
|
-
open_entries: list[OrderChainEntry]
|
|
523
|
-
total_cost_per_unit: Decimal | None = None
|
|
524
|
-
|
|
525
|
-
@model_validator(mode="before")
|
|
526
|
-
@classmethod
|
|
527
|
-
def validate_price_effects(cls, data: Any) -> Any:
|
|
528
|
-
return set_sign_for(
|
|
529
|
-
data,
|
|
530
|
-
[
|
|
531
|
-
"total_fees",
|
|
532
|
-
"total_commissions",
|
|
533
|
-
"realized_gain",
|
|
534
|
-
"realized_gain_with_fees",
|
|
535
|
-
"total_opening_cost",
|
|
536
|
-
"total_closing_cost",
|
|
537
|
-
"total_cost",
|
|
538
|
-
"total_cost_per_unit",
|
|
539
|
-
],
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
class OrderChain(TastytradeData):
|
|
544
|
-
"""
|
|
545
|
-
Dataclass containing information about an order chain: a group of orders
|
|
546
|
-
for a specific underlying, such as total P/L, rolls, current P/L in a
|
|
547
|
-
symbol, etc.
|
|
548
|
-
"""
|
|
549
|
-
|
|
550
|
-
id: int
|
|
551
|
-
account_number: str
|
|
552
|
-
description: str
|
|
553
|
-
underlying_symbol: str
|
|
554
|
-
computed_data: ComputedData
|
|
555
|
-
lite_nodes: list[OrderChainNode]
|
|
556
|
-
lite_nodes_sizes: int | None = None
|
|
557
|
-
updated_at: datetime | None = None
|
|
558
|
-
created_at: datetime | None = None
|
|
@@ -233,7 +233,7 @@ class Customer(TastytradeData):
|
|
|
233
233
|
desk_customer_id: str | None = None
|
|
234
234
|
entity: CustomerEntity | None = None
|
|
235
235
|
family_member_names: str | None = None
|
|
236
|
-
has_institutional_assets: str | None = None
|
|
236
|
+
has_institutional_assets: str | bool | None = None
|
|
237
237
|
industry_affiliation_firm: str | None = None
|
|
238
238
|
is_investment_adviser: bool | None = None
|
|
239
239
|
listed_affiliation_symbol: str | None = None
|
|
@@ -289,9 +289,7 @@ class Session:
|
|
|
289
289
|
self.streamer_expiration = now_in_new_york()
|
|
290
290
|
self.refresh()
|
|
291
291
|
|
|
292
|
-
def _streamer_refresh(self) -> None:
|
|
293
|
-
# Pull streamer tokens and urls
|
|
294
|
-
data = self._get("/api-quote-tokens")
|
|
292
|
+
def _streamer_refresh(self, data: Any) -> None:
|
|
295
293
|
# Auth token for dxfeed websocket
|
|
296
294
|
self.streamer_token = data["token"]
|
|
297
295
|
# URL for dxfeed websocket
|
|
@@ -331,7 +329,8 @@ class Session:
|
|
|
331
329
|
self.async_client.headers.update(auth_headers)
|
|
332
330
|
# update the streamer token if necessary
|
|
333
331
|
if not self.is_test and self.streamer_expiration < self.session_expiration:
|
|
334
|
-
self.
|
|
332
|
+
data = self._get("/api-quote-tokens")
|
|
333
|
+
self._streamer_refresh(data)
|
|
335
334
|
|
|
336
335
|
async def a_refresh(self) -> None:
|
|
337
336
|
"""
|
|
@@ -365,19 +364,11 @@ class Session:
|
|
|
365
364
|
if not self.is_test and self.streamer_expiration < self.session_expiration:
|
|
366
365
|
# Pull streamer tokens and urls
|
|
367
366
|
data = await self._a_get("/api-quote-tokens")
|
|
368
|
-
|
|
369
|
-
self.streamer_token = data["token"]
|
|
370
|
-
# URL for dxfeed websocket
|
|
371
|
-
self.dxlink_url = data["dxlink-url"]
|
|
372
|
-
self.streamer_expiration = datetime.fromisoformat(
|
|
373
|
-
data["expires-at"].replace("Z", "+00:00")
|
|
374
|
-
)
|
|
367
|
+
self._streamer_refresh(data)
|
|
375
368
|
|
|
376
369
|
async def a_get_customer(self) -> Customer:
|
|
377
370
|
"""
|
|
378
371
|
Gets the customer dict from the API.
|
|
379
|
-
|
|
380
|
-
:return: a Tastytrade 'Customer' object in JSON format.
|
|
381
372
|
"""
|
|
382
373
|
data = await self._a_get("/customers/me")
|
|
383
374
|
return Customer(**data)
|
|
@@ -385,8 +376,6 @@ class Session:
|
|
|
385
376
|
def get_customer(self) -> Customer:
|
|
386
377
|
"""
|
|
387
378
|
Gets the customer dict from the API.
|
|
388
|
-
|
|
389
|
-
:return: a Tastytrade 'Customer' object in JSON format.
|
|
390
379
|
"""
|
|
391
380
|
data = self._get("/customers/me")
|
|
392
381
|
return Customer(**data)
|
|
@@ -394,8 +383,6 @@ class Session:
|
|
|
394
383
|
async def a_validate(self) -> bool:
|
|
395
384
|
"""
|
|
396
385
|
Validates the current session by sending a request to the API.
|
|
397
|
-
|
|
398
|
-
:return: True if the session is valid and False otherwise.
|
|
399
386
|
"""
|
|
400
387
|
response = await self.async_client.post("/sessions/validate")
|
|
401
388
|
return response.status_code // 100 == 2
|
|
@@ -403,8 +390,6 @@ class Session:
|
|
|
403
390
|
def validate(self) -> bool:
|
|
404
391
|
"""
|
|
405
392
|
Validates the current session by sending a request to the API.
|
|
406
|
-
|
|
407
|
-
:return: True if the session is valid and False otherwise.
|
|
408
393
|
"""
|
|
409
394
|
response = self.sync_client.post("/sessions/validate")
|
|
410
395
|
return response.status_code // 100 == 2
|
|
@@ -420,6 +405,7 @@ class Session:
|
|
|
420
405
|
del attrs["sync_client"]
|
|
421
406
|
attrs["session_expiration"] = self.session_expiration.strftime(_fmt)
|
|
422
407
|
attrs["streamer_expiration"] = self.streamer_expiration.strftime(_fmt)
|
|
408
|
+
attrs["headers"] = dict(self.sync_client.headers.copy())
|
|
423
409
|
return json.dumps(attrs)
|
|
424
410
|
|
|
425
411
|
@classmethod
|
|
@@ -427,17 +413,11 @@ class Session:
|
|
|
427
413
|
"""
|
|
428
414
|
Create a new Session object from a serialized string.
|
|
429
415
|
"""
|
|
430
|
-
deserialized = json.loads(serialized)
|
|
416
|
+
deserialized: dict[str, Any] = json.loads(serialized)
|
|
417
|
+
headers = deserialized.pop("headers")
|
|
431
418
|
self = cls.__new__(cls)
|
|
432
419
|
self.__dict__ = deserialized
|
|
433
420
|
base_url = CERT_URL if self.is_test else API_URL
|
|
434
|
-
headers = {
|
|
435
|
-
"Accept": "application/json",
|
|
436
|
-
"Content-Type": "application/json",
|
|
437
|
-
"Authorization": self.session_token
|
|
438
|
-
if "user" in deserialized
|
|
439
|
-
else f"Bearer {self.session_token}",
|
|
440
|
-
}
|
|
441
421
|
self.session_expiration = datetime.strptime(
|
|
442
422
|
deserialized["session_expiration"], _fmt
|
|
443
423
|
)
|
|
@@ -29,12 +29,7 @@ from tastytrade.dxfeed import (
|
|
|
29
29
|
Trade,
|
|
30
30
|
Underlying,
|
|
31
31
|
)
|
|
32
|
-
from tastytrade.order import
|
|
33
|
-
InstrumentType,
|
|
34
|
-
OrderChain,
|
|
35
|
-
PlacedComplexOrder,
|
|
36
|
-
PlacedOrder,
|
|
37
|
-
)
|
|
32
|
+
from tastytrade.order import InstrumentType, PlacedComplexOrder, PlacedOrder
|
|
38
33
|
from tastytrade.session import Session
|
|
39
34
|
from tastytrade.utils import TastytradeData, TastytradeError, set_sign_for
|
|
40
35
|
from tastytrade.watchlists import Watchlist
|
|
@@ -133,7 +128,6 @@ AlertType: TypeAlias = (
|
|
|
133
128
|
| ExternalTransaction
|
|
134
129
|
| PlacedComplexOrder
|
|
135
130
|
| PlacedOrder
|
|
136
|
-
| OrderChain
|
|
137
131
|
| CurrentPosition
|
|
138
132
|
| QuoteAlert
|
|
139
133
|
| TradingStatus
|
|
@@ -146,7 +140,6 @@ MAP_ALERTS: dict[str, type[AlertType]] = {
|
|
|
146
140
|
"ComplexOrder": PlacedComplexOrder,
|
|
147
141
|
"ExternalTransaction": ExternalTransaction,
|
|
148
142
|
"Order": PlacedOrder,
|
|
149
|
-
"OrderChain": OrderChain,
|
|
150
143
|
"CurrentPosition": CurrentPosition,
|
|
151
144
|
"QuoteAlert": QuoteAlert,
|
|
152
145
|
"TradingStatus": TradingStatus,
|
|
@@ -761,7 +754,7 @@ class DXLinkStreamer:
|
|
|
761
754
|
}
|
|
762
755
|
|
|
763
756
|
def dict_from_schema(event_class: Any) -> dict[str, list[Any]]:
|
|
764
|
-
schema = event_class.
|
|
757
|
+
schema = event_class.model_json_schema()
|
|
765
758
|
return {schema["title"]: list(schema["properties"].keys())}
|
|
766
759
|
|
|
767
760
|
cls = MAP_EVENTS[event_type]
|