tastytrade 11.0.4__py3-none-any.whl → 11.1.0__py3-none-any.whl
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/__init__.py +1 -2
- tastytrade/account.py +0 -173
- tastytrade/dxfeed/event.py +0 -2
- tastytrade/instruments.py +1 -1
- tastytrade/market_sessions.py +19 -0
- tastytrade/order.py +5 -124
- tastytrade/session.py +8 -28
- tastytrade/streamer.py +2 -9
- tastytrade/utils.py +17 -24
- {tastytrade-11.0.4.dist-info → tastytrade-11.1.0.dist-info}/METADATA +1 -1
- {tastytrade-11.0.4.dist-info → tastytrade-11.1.0.dist-info}/RECORD +13 -13
- {tastytrade-11.0.4.dist-info → tastytrade-11.1.0.dist-info}/WHEEL +0 -0
- {tastytrade-11.0.4.dist-info → tastytrade-11.1.0.dist-info}/licenses/LICENSE +0 -0
tastytrade/__init__.py
CHANGED
|
@@ -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}"
|
tastytrade/account.py
CHANGED
|
@@ -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]
|
tastytrade/dxfeed/event.py
CHANGED
tastytrade/instruments.py
CHANGED
|
@@ -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
|
tastytrade/market_sessions.py
CHANGED
|
@@ -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)
|
tastytrade/order.py
CHANGED
|
@@ -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
|
tastytrade/session.py
CHANGED
|
@@ -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
|
)
|
tastytrade/streamer.py
CHANGED
|
@@ -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]
|
tastytrade/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Any, Type, TypeVar, cast
|
|
|
6
6
|
from zoneinfo import ZoneInfo
|
|
7
7
|
|
|
8
8
|
from httpx import AsyncClient, Client, Response
|
|
9
|
+
from pandas import Timestamp
|
|
9
10
|
from pandas_market_calendars import get_calendar # type: ignore[import-untyped]
|
|
10
11
|
from pydantic import BaseModel, ConfigDict
|
|
11
12
|
|
|
@@ -29,8 +30,6 @@ class PriceEffect(str, Enum):
|
|
|
29
30
|
def now_in_new_york() -> datetime:
|
|
30
31
|
"""
|
|
31
32
|
Gets the current time in the New York timezone.
|
|
32
|
-
|
|
33
|
-
:return: current time as datetime
|
|
34
33
|
"""
|
|
35
34
|
return datetime.now(TZ)
|
|
36
35
|
|
|
@@ -38,20 +37,32 @@ def now_in_new_york() -> datetime:
|
|
|
38
37
|
def today_in_new_york() -> date:
|
|
39
38
|
"""
|
|
40
39
|
Gets the current date in the New York timezone.
|
|
41
|
-
|
|
42
|
-
:return: current date
|
|
43
40
|
"""
|
|
44
41
|
return now_in_new_york().date()
|
|
45
42
|
|
|
46
43
|
|
|
44
|
+
def is_market_open_now() -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Check if the market is currently open.
|
|
47
|
+
"""
|
|
48
|
+
today = today_in_new_york()
|
|
49
|
+
sched = NYSE.schedule(start_date=today, end_date=today)
|
|
50
|
+
if sched.empty:
|
|
51
|
+
# Closed full day (weekend or holiday)
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# Use iloc[0] since schedule has only one row for a single day
|
|
55
|
+
market_open: Timestamp = sched.iloc[0]["market_open"]
|
|
56
|
+
market_close: Timestamp = sched.iloc[0]["market_close"]
|
|
57
|
+
return market_open <= now_in_new_york() < market_close
|
|
58
|
+
|
|
59
|
+
|
|
47
60
|
def is_market_open_on(day: date | None = None) -> bool:
|
|
48
61
|
"""
|
|
49
62
|
Returns whether the market was/is/will be open at ANY point
|
|
50
63
|
during the given day.
|
|
51
64
|
|
|
52
65
|
:param day: date to check. If not provided defaults to current NY date.
|
|
53
|
-
|
|
54
|
-
:return: whether the market opens on given day
|
|
55
66
|
"""
|
|
56
67
|
day = day or today_in_new_york()
|
|
57
68
|
date_range = NYSE.valid_days(day, day)
|
|
@@ -64,8 +75,6 @@ def get_third_friday(day: date | None = None) -> date:
|
|
|
64
75
|
or the monthly expiration associated with today's month.
|
|
65
76
|
|
|
66
77
|
:param day: date to check. If not provided defaults to current NY date.
|
|
67
|
-
|
|
68
|
-
:return: the associated monthly
|
|
69
78
|
"""
|
|
70
79
|
day = (day or today_in_new_york()).replace(day=1) + timedelta(weeks=2)
|
|
71
80
|
while day.weekday() != 4: # Friday
|
|
@@ -76,8 +85,6 @@ def get_third_friday(day: date | None = None) -> date:
|
|
|
76
85
|
def get_tasty_monthly() -> date:
|
|
77
86
|
"""
|
|
78
87
|
Gets the monthly expiration closest to 45 days from the current date.
|
|
79
|
-
|
|
80
|
-
:return: the closest to 45 DTE monthly expiration
|
|
81
88
|
"""
|
|
82
89
|
day = today_in_new_york()
|
|
83
90
|
exp1 = get_third_friday(day + timedelta(weeks=4))
|
|
@@ -101,8 +108,6 @@ def get_future_fx_monthly(day: date | None = None) -> date:
|
|
|
101
108
|
Wednesday.
|
|
102
109
|
|
|
103
110
|
:param day: date to check. If not provided defaults to current NY date.
|
|
104
|
-
|
|
105
|
-
:return: the associated monthly
|
|
106
111
|
"""
|
|
107
112
|
day = (day or today_in_new_york()).replace(day=1) + timedelta(weeks=1)
|
|
108
113
|
while day.weekday() != 2: # Wednesday
|
|
@@ -120,8 +125,6 @@ def get_future_treasury_monthly(day: date | None = None) -> date:
|
|
|
120
125
|
business day prior.
|
|
121
126
|
|
|
122
127
|
:param day: date to check. If not provided defaults to current NY date.
|
|
123
|
-
|
|
124
|
-
:return: the associated monthly
|
|
125
128
|
"""
|
|
126
129
|
day = day or today_in_new_york()
|
|
127
130
|
last_day = _get_last_day_of_month(day)
|
|
@@ -143,8 +146,6 @@ def get_future_metal_monthly(day: date | None = None) -> date:
|
|
|
143
146
|
which case they expire on the prior business day.
|
|
144
147
|
|
|
145
148
|
:param day: date to check. If not provided defaults to current NY date.
|
|
146
|
-
|
|
147
|
-
:return: the associated monthly
|
|
148
149
|
"""
|
|
149
150
|
day = day or today_in_new_york()
|
|
150
151
|
last_day = _get_last_day_of_month(day)
|
|
@@ -164,8 +165,6 @@ def get_future_grain_monthly(day: date | None = None) -> date:
|
|
|
164
165
|
least 2 business days, the last business day of the month.
|
|
165
166
|
|
|
166
167
|
:param day: date to check. If not provided defaults to current NY date.
|
|
167
|
-
|
|
168
|
-
:return: the associated monthly
|
|
169
168
|
"""
|
|
170
169
|
day = day or today_in_new_york()
|
|
171
170
|
last_day = _get_last_day_of_month(day)
|
|
@@ -185,8 +184,6 @@ def get_future_oil_monthly(day: date | None = None) -> date:
|
|
|
185
184
|
they expire 7 business days prior to the 25th day of the month.
|
|
186
185
|
|
|
187
186
|
:param day: date to check. If not provided defaults to current NY date.
|
|
188
|
-
|
|
189
|
-
:return: the associated monthly
|
|
190
187
|
"""
|
|
191
188
|
last_day = (day or today_in_new_york()).replace(day=25)
|
|
192
189
|
first_day = last_day.replace(day=1)
|
|
@@ -201,8 +198,6 @@ def get_future_index_monthly(day: date | None = None) -> date:
|
|
|
201
198
|
month.
|
|
202
199
|
|
|
203
200
|
:param day: date to check. If not provided defaults to current NY date.
|
|
204
|
-
|
|
205
|
-
:return: the associated monthly
|
|
206
201
|
"""
|
|
207
202
|
day = day or today_in_new_york()
|
|
208
203
|
last_day = _get_last_day_of_month(day)
|
|
@@ -224,8 +219,6 @@ def _dasherize(s: str) -> str:
|
|
|
224
219
|
Converts a string from snake case to dasherized.
|
|
225
220
|
|
|
226
221
|
:param s: string to convert
|
|
227
|
-
|
|
228
|
-
:return: dasherized string
|
|
229
222
|
"""
|
|
230
223
|
return s.replace("_", "-")
|
|
231
224
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
tastytrade/__init__.py,sha256=
|
|
2
|
-
tastytrade/account.py,sha256=
|
|
3
|
-
tastytrade/instruments.py,sha256=
|
|
1
|
+
tastytrade/__init__.py,sha256=DvTWfw-6-YKHKsRPptZDHxdn4iqnRK8hTVzd8lSfJxE,490
|
|
2
|
+
tastytrade/account.py,sha256=Tekp-k1V6PIne9saasRyhkmFp3Zj4bypWtuk-KNVp58,50850
|
|
3
|
+
tastytrade/instruments.py,sha256=QZHakLgxsgscZx7ouz1LgIuahlHM2n0AfY9UQBdlEoM,46803
|
|
4
4
|
tastytrade/market_data.py,sha256=iyBpleKvDWfXReo6DuKbXP-gN1Yv8iyvNs9z2x5Gxig,5940
|
|
5
|
-
tastytrade/market_sessions.py,sha256=
|
|
5
|
+
tastytrade/market_sessions.py,sha256=qx1oSOqmFzrGVN_IwxAbG7oSEIob2H_RTEX8zTlFoOA,3995
|
|
6
6
|
tastytrade/metrics.py,sha256=LqpUZIR4L2bQwawf_L4gJ3Pjg_S72eURrJpu4ebKtpU,7495
|
|
7
|
-
tastytrade/order.py,sha256=
|
|
7
|
+
tastytrade/order.py,sha256=NUAL8Ac8mH0CULe1g1Xz_A7m5ZeKPkLjFPCF5_3D6m0,12032
|
|
8
8
|
tastytrade/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
tastytrade/search.py,sha256=LdoVEhiYNvtolXlf_jAVZUtA2ymUWOvHMhQxJxuVt_A,1529
|
|
10
|
-
tastytrade/session.py,sha256=
|
|
11
|
-
tastytrade/streamer.py,sha256=
|
|
12
|
-
tastytrade/utils.py,sha256=
|
|
10
|
+
tastytrade/session.py,sha256=iV4M_dBbpi9H5umiVBVDYm0I-7823xgqiOc6cVN5Xu8,14832
|
|
11
|
+
tastytrade/streamer.py,sha256=VTZBlPfiYIwsg_hoYBvofjTPExubYSGmxbfFo-ELrj4,32540
|
|
12
|
+
tastytrade/utils.py,sha256=65Zr8hz3oUNmh8WYL2YGYt2pRBafljyLghfHLU60mtA,12851
|
|
13
13
|
tastytrade/watchlists.py,sha256=K9jpgXi9a9dklGl-jCXzpbM0tOd836GSkXa4fySeH3Q,8657
|
|
14
14
|
tastytrade/dxfeed/__init__.py,sha256=GmC0aKtiUjs7aqbX7PeqMaROxqalwzHOnJOMJn8TaZk,458
|
|
15
15
|
tastytrade/dxfeed/candle.py,sha256=j9nuWftzOT_qGDTZNNfFIABZp_n_5Gi7OFm5KPK2dnc,1757
|
|
16
|
-
tastytrade/dxfeed/event.py,sha256=
|
|
16
|
+
tastytrade/dxfeed/event.py,sha256=C7ANFpblgk4MfNivvat25ox0jHmm6cCzQGYiXlsi81s,6021
|
|
17
17
|
tastytrade/dxfeed/greeks.py,sha256=Q9cGrXswtWGrti8eN6Owhvs9vC5YcdtW5mJrN92_4eA,1071
|
|
18
18
|
tastytrade/dxfeed/profile.py,sha256=sj-HCN7qrQvP61jEOzsEpwDsl5M_VMR_KekyRyjdsXY,1896
|
|
19
19
|
tastytrade/dxfeed/quote.py,sha256=MLRe9NQZI1DbY5ZgYnyUh52Z9824uep9ZYgngbRhmbk,944
|
|
@@ -22,7 +22,7 @@ tastytrade/dxfeed/theoprice.py,sha256=L5aH--F_6xLZCSYZ4APpzlihbW0-cYEwRdeGVI-aNa
|
|
|
22
22
|
tastytrade/dxfeed/timeandsale.py,sha256=QuMFoccq8x3c2y6s3DnwBNIVTrLS6OPqV6GmCNoXQEQ,1903
|
|
23
23
|
tastytrade/dxfeed/trade.py,sha256=qNo4oKb7iq0Opoq3FCBEUUcGGF6udda1bD0eKQVty_0,1402
|
|
24
24
|
tastytrade/dxfeed/underlying.py,sha256=YYqJNlmrlt6Kpg0F6voQ18g60obXiYTVlroXirBWPR8,1226
|
|
25
|
-
tastytrade-11.0.
|
|
26
|
-
tastytrade-11.0.
|
|
27
|
-
tastytrade-11.0.
|
|
28
|
-
tastytrade-11.0.
|
|
25
|
+
tastytrade-11.1.0.dist-info/METADATA,sha256=MANTjAy63Lj770EI56KQ06oVm9pOp69g2svr_x8yvmc,10904
|
|
26
|
+
tastytrade-11.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
27
|
+
tastytrade-11.1.0.dist-info/licenses/LICENSE,sha256=enBkMN4OsfLt6Z_AsrGC7u5dAJkCEODnoN7BwMCzSfc,1072
|
|
28
|
+
tastytrade-11.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|