tastytrade 11.1.0__tar.gz → 12.0.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.
Files changed (93) hide show
  1. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/pull_request_template.md +1 -1
  2. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/workflows/python-app.yml +1 -2
  3. tastytrade-12.0.0/.python-version +1 -0
  4. {tastytrade-11.1.0 → tastytrade-12.0.0}/Makefile +1 -1
  5. {tastytrade-11.1.0 → tastytrade-12.0.0}/PKG-INFO +15 -26
  6. {tastytrade-11.1.0 → tastytrade-12.0.0}/README.md +7 -20
  7. tastytrade-12.0.0/docs/account-streamer.rst +59 -0
  8. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/accounts.rst +6 -6
  9. tastytrade-12.0.0/docs/data-streamer.rst +73 -0
  10. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/index.rst +1 -2
  11. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/instruments.rst +12 -12
  12. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/market-data.rst +2 -2
  13. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/market-sessions.rst +3 -3
  14. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/orders.rst +16 -16
  15. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/sessions.rst +5 -12
  16. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/watchlists.rst +4 -4
  17. {tastytrade-11.1.0 → tastytrade-12.0.0}/pyproject.toml +9 -10
  18. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/__init__.py +1 -1
  19. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/account.py +48 -532
  20. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/candle.py +2 -4
  21. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/event.py +3 -3
  22. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/quote.py +26 -3
  23. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/instruments.py +54 -522
  24. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/market_data.py +4 -52
  25. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/market_sessions.py +6 -43
  26. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/metrics.py +10 -67
  27. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/order.py +4 -0
  28. tastytrade-12.0.0/tastytrade/search.py +29 -0
  29. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/session.py +112 -156
  30. tastytrade-12.0.0/tastytrade/streamer.py +696 -0
  31. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/utils.py +2 -85
  32. tastytrade-12.0.0/tastytrade/watchlists.py +182 -0
  33. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/conftest.py +7 -8
  34. tastytrade-12.0.0/tests/test_account.py +259 -0
  35. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/test_dxfeed.py +0 -3
  36. tastytrade-12.0.0/tests/test_instruments.py +115 -0
  37. tastytrade-12.0.0/tests/test_market_data.py +25 -0
  38. tastytrade-12.0.0/tests/test_market_sessions.py +28 -0
  39. tastytrade-12.0.0/tests/test_metrics.py +29 -0
  40. tastytrade-12.0.0/tests/test_search.py +17 -0
  41. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/test_session.py +3 -18
  42. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/test_streamer.py +3 -35
  43. tastytrade-12.0.0/tests/test_watchlists.py +63 -0
  44. {tastytrade-11.1.0 → tastytrade-12.0.0}/uv.lock +264 -795
  45. tastytrade-11.1.0/.python-version +0 -1
  46. tastytrade-11.1.0/docs/account-streamer.rst +0 -82
  47. tastytrade-11.1.0/docs/data-streamer.rst +0 -189
  48. tastytrade-11.1.0/docs/sync-async.rst +0 -27
  49. tastytrade-11.1.0/tastytrade/search.py +0 -47
  50. tastytrade-11.1.0/tastytrade/streamer.py +0 -886
  51. tastytrade-11.1.0/tastytrade/watchlists.py +0 -293
  52. tastytrade-11.1.0/tests/test_account.py +0 -419
  53. tastytrade-11.1.0/tests/test_instruments.py +0 -201
  54. tastytrade-11.1.0/tests/test_market_data.py +0 -37
  55. tastytrade-11.1.0/tests/test_market_sessions.py +0 -43
  56. tastytrade-11.1.0/tests/test_metrics.py +0 -45
  57. tastytrade-11.1.0/tests/test_search.py +0 -24
  58. tastytrade-11.1.0/tests/test_watchlists.py +0 -109
  59. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/CONTRIBUTING.md +0 -0
  60. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/FUNDING.yml +0 -0
  61. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/workflows/python-publish-test.yml +0 -0
  62. {tastytrade-11.1.0 → tastytrade-12.0.0}/.github/workflows/python-publish.yml +0 -0
  63. {tastytrade-11.1.0 → tastytrade-12.0.0}/.gitignore +0 -0
  64. {tastytrade-11.1.0 → tastytrade-12.0.0}/.readthedocs.yaml +0 -0
  65. {tastytrade-11.1.0 → tastytrade-12.0.0}/LICENSE +0 -0
  66. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/Makefile +0 -0
  67. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/account.rst +0 -0
  68. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/dxfeed.rst +0 -0
  69. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/instruments.rst +0 -0
  70. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/market-data.rst +0 -0
  71. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/market-sessions.rst +0 -0
  72. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/metrics.rst +0 -0
  73. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/order.rst +0 -0
  74. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/search.rst +0 -0
  75. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/session.rst +0 -0
  76. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/streamer.rst +0 -0
  77. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/utils.rst +0 -0
  78. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/api/watchlists.rst +0 -0
  79. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/conf.py +0 -0
  80. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/img/netliq.png +0 -0
  81. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/installation.rst +0 -0
  82. {tastytrade-11.1.0 → tastytrade-12.0.0}/docs/make.bat +0 -0
  83. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/__init__.py +0 -0
  84. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/greeks.py +0 -0
  85. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/profile.py +0 -0
  86. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/summary.py +0 -0
  87. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/theoprice.py +0 -0
  88. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/timeandsale.py +0 -0
  89. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/trade.py +0 -0
  90. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/dxfeed/underlying.py +0 -0
  91. {tastytrade-11.1.0 → tastytrade-12.0.0}/tastytrade/py.typed +0 -0
  92. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/__init__.py +0 -0
  93. {tastytrade-11.1.0 → tastytrade-12.0.0}/tests/test_utils.py +0 -0
@@ -5,8 +5,8 @@ Fixes ...
5
5
 
6
6
  ## Pre-merge checklist
7
7
  - [ ] Code formatted correctly (check with `make lint`)
8
- - [ ] Code implemented for both sync and async
9
8
  - [ ] Passing tests locally (check with `make test`, make sure you have `TT_REFRESH`, `TT_SECRET`, and `TT_ACCOUNT` environment variables set)
10
9
  - [ ] New tests added (if applicable)
10
+ - [ ] Docs updated (if applicable)
11
11
 
12
12
  Please note that, in order to pass the tests, you'll need to set up your Tastytrade credentials as repository secrets on your local fork. Read more at CONTRIBUTING.md.
@@ -13,7 +13,6 @@ jobs:
13
13
  max-parallel: 1
14
14
  matrix:
15
15
  python-version:
16
- - "3.10"
17
16
  - "3.11"
18
17
  - "3.12"
19
18
  - "3.13"
@@ -39,7 +38,7 @@ jobs:
39
38
  uv run mypy tastytrade/
40
39
  - name: Test with pytest
41
40
  run: |
42
- uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95
41
+ uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95 -v
43
42
  env:
44
43
  TT_REFRESH_SANDBOX: ${{ secrets.TT_REFRESH_SANDBOX }}
45
44
  TT_SECRET_SANDBOX: ${{ secrets.TT_SECRET_SANDBOX }}
@@ -0,0 +1 @@
1
+ 3.11
@@ -11,7 +11,7 @@ lint:
11
11
  uv run mypy tastytrade/
12
12
 
13
13
  test:
14
- uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95
14
+ uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95 -v
15
15
 
16
16
  docs:
17
17
  uv run -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs/ docs/_build/
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tastytrade
3
- Version: 11.1.0
4
- Summary: An unofficial, sync/async SDK for Tastytrade!
3
+ Version: 12.0.0
4
+ Summary: An unofficial, typed, asynchronous Python SDK for Tastytrade!
5
5
  Project-URL: Homepage, https://github.com/tastyware/tastytrade
6
6
  Project-URL: Documentation, https://tastyworks-api.rtfd.io
7
7
  Project-URL: Funding, https://github.com/sponsors/tastyware
@@ -32,7 +32,9 @@ License: MIT License
32
32
  License-File: LICENSE
33
33
  Classifier: Development Status :: 5 - Production/Stable
34
34
  Classifier: Environment :: Console
35
+ Classifier: Framework :: AnyIO
35
36
  Classifier: Framework :: AsyncIO
37
+ Classifier: Framework :: Trio
36
38
  Classifier: Intended Audience :: Developers
37
39
  Classifier: Intended Audience :: Financial and Insurance Industry
38
40
  Classifier: Intended Audience :: Information Technology
@@ -44,7 +46,6 @@ Classifier: Operating System :: Unix
44
46
  Classifier: Programming Language :: Python
45
47
  Classifier: Programming Language :: Python :: 3
46
48
  Classifier: Programming Language :: Python :: 3 :: Only
47
- Classifier: Programming Language :: Python :: 3.10
48
49
  Classifier: Programming Language :: Python :: 3.11
49
50
  Classifier: Programming Language :: Python :: 3.12
50
51
  Classifier: Programming Language :: Python :: 3.13
@@ -52,11 +53,12 @@ Classifier: Programming Language :: Python :: 3.14
52
53
  Classifier: Topic :: Internet :: WWW/HTTP
53
54
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
54
55
  Classifier: Typing :: Typed
55
- Requires-Python: >=3.9
56
+ Requires-Python: >=3.11
57
+ Requires-Dist: anyio>=4.12.1
58
+ Requires-Dist: httpx-ws>=0.7.2
56
59
  Requires-Dist: httpx>=0.28.1
57
60
  Requires-Dist: pandas-market-calendars>=5.1.1
58
- Requires-Dist: pydantic>=2.11.9
59
- Requires-Dist: websockets>=15.0.1
61
+ Requires-Dist: pydantic>=2.12.0
60
62
  Description-Content-Type: text/markdown
61
63
 
62
64
  [![Docs](https://readthedocs.org/projects/tastyworks-api/badge/?version=latest)](https://tastyworks-api.readthedocs.io/en/latest/?badge=latest)
@@ -71,8 +73,7 @@ A simple, reverse-engineered SDK for Tastytrade built on their (now mostly publi
71
73
  ## Features
72
74
 
73
75
  - Up to 10x less code than using the API directly
74
- - Sync/async functions for all endpoints
75
- - Powerful websocket implementation for account alerts and data streaming, with support for auto-reconnection and reconnection callbacks
76
+ - Powerful websocket implementation for account alerts and data streaming
76
77
  - 100% typed, with Pydantic models for all JSON responses from the API
77
78
  - 95%+ unit test coverage
78
79
  - Comprehensive documentation
@@ -126,8 +127,8 @@ Note that this is asynchronous code, so you can't run it as is unless you're usi
126
127
  ```python
127
128
  from tastytrade import Account
128
129
 
129
- account = Account.get(session)[0]
130
- positions = account.get_positions(session)
130
+ account = (await Account.get(session))[0]
131
+ positions = await account.get_positions(session)
131
132
  print(positions[0])
132
133
  ```
133
134
 
@@ -135,18 +136,6 @@ print(positions[0])
135
136
  >>> CurrentPosition(account_number='5WX01234', symbol='IAU', instrument_type=<InstrumentType.EQUITY: 'Equity'>, underlying_symbol='IAU', quantity=Decimal('20'), quantity_direction='Long', close_price=Decimal('37.09'), average_open_price=Decimal('37.51'), average_yearly_market_close_price=Decimal('37.51'), average_daily_market_close_price=Decimal('37.51'), multiplier=1, cost_effect=<PriceEffect.CREDIT: 'Credit'>, is_suppressed=False, is_frozen=False, realized_day_gain=Decimal('7.888'), realized_day_gain_date=datetime.date(2023, 5, 19), realized_today=Decimal('-0.512'), realized_today_date=datetime.date(2023, 5, 19), created_at=datetime.datetime(2023, 3, 31, 14, 38, 32, 58000, tzinfo=datetime.timezone.utc), updated_at=datetime.datetime(2023, 5, 19, 16, 56, 51, 920000, tzinfo=datetime.timezone.utc), mark=None, mark_price=None, restricted_quantity=Decimal('0'), expires_at=None, fixing_price=None, deliverable_type=None)
136
137
  ```
137
138
 
138
- ## Sync/async wrappers
139
-
140
- The code from above can be rewritten asynchronously:
141
-
142
- ```python
143
- from tastytrade import Account
144
-
145
- account = (await Account.a_get(session))[0]
146
- positions = await account.a_get_positions(session)
147
- print(positions[0])
148
- ```
149
-
150
139
  ## Placing an order
151
140
 
152
141
  ```python
@@ -155,8 +144,8 @@ from tastytrade import Account
155
144
  from tastytrade.instruments import Equity
156
145
  from tastytrade.order import NewOrder, OrderAction, OrderTimeInForce, OrderType
157
146
 
158
- account = Account.get(session, '5WX01234')
159
- symbol = Equity.get(session, 'USO')
147
+ account = await Account.get(session, '5WX01234')
148
+ symbol = await Equity.get(session, 'USO')
160
149
  leg = symbol.build_leg(Decimal('5'), OrderAction.BUY_TO_OPEN) # buy to open 5 shares
161
150
 
162
151
  order = NewOrder(
@@ -165,7 +154,7 @@ order = NewOrder(
165
154
  legs=[leg], # you can have multiple legs in an order
166
155
  price=Decimal('-10') # limit price, $10/share debit for a total value of $50
167
156
  )
168
- response = account.place_order(session, order, dry_run=True) # a test order
157
+ response = await account.place_order(session, order, dry_run=True) # a test order
169
158
  print(response)
170
159
  ```
171
160
 
@@ -181,7 +170,7 @@ from tastytrade.dxfeed import Greeks
181
170
  from tastytrade.instruments import get_option_chain
182
171
  from tastytrade.utils import get_tasty_monthly
183
172
 
184
- chain = get_option_chain(session, 'SPLG')
173
+ chain = await get_option_chain(session, 'SPLG')
185
174
  exp = get_tasty_monthly() # 45 DTE expiration!
186
175
  subs_list = [chain[exp][0].streamer_symbol]
187
176
 
@@ -10,8 +10,7 @@ A simple, reverse-engineered SDK for Tastytrade built on their (now mostly publi
10
10
  ## Features
11
11
 
12
12
  - Up to 10x less code than using the API directly
13
- - Sync/async functions for all endpoints
14
- - Powerful websocket implementation for account alerts and data streaming, with support for auto-reconnection and reconnection callbacks
13
+ - Powerful websocket implementation for account alerts and data streaming
15
14
  - 100% typed, with Pydantic models for all JSON responses from the API
16
15
  - 95%+ unit test coverage
17
16
  - Comprehensive documentation
@@ -65,8 +64,8 @@ Note that this is asynchronous code, so you can't run it as is unless you're usi
65
64
  ```python
66
65
  from tastytrade import Account
67
66
 
68
- account = Account.get(session)[0]
69
- positions = account.get_positions(session)
67
+ account = (await Account.get(session))[0]
68
+ positions = await account.get_positions(session)
70
69
  print(positions[0])
71
70
  ```
72
71
 
@@ -74,18 +73,6 @@ print(positions[0])
74
73
  >>> CurrentPosition(account_number='5WX01234', symbol='IAU', instrument_type=<InstrumentType.EQUITY: 'Equity'>, underlying_symbol='IAU', quantity=Decimal('20'), quantity_direction='Long', close_price=Decimal('37.09'), average_open_price=Decimal('37.51'), average_yearly_market_close_price=Decimal('37.51'), average_daily_market_close_price=Decimal('37.51'), multiplier=1, cost_effect=<PriceEffect.CREDIT: 'Credit'>, is_suppressed=False, is_frozen=False, realized_day_gain=Decimal('7.888'), realized_day_gain_date=datetime.date(2023, 5, 19), realized_today=Decimal('-0.512'), realized_today_date=datetime.date(2023, 5, 19), created_at=datetime.datetime(2023, 3, 31, 14, 38, 32, 58000, tzinfo=datetime.timezone.utc), updated_at=datetime.datetime(2023, 5, 19, 16, 56, 51, 920000, tzinfo=datetime.timezone.utc), mark=None, mark_price=None, restricted_quantity=Decimal('0'), expires_at=None, fixing_price=None, deliverable_type=None)
75
74
  ```
76
75
 
77
- ## Sync/async wrappers
78
-
79
- The code from above can be rewritten asynchronously:
80
-
81
- ```python
82
- from tastytrade import Account
83
-
84
- account = (await Account.a_get(session))[0]
85
- positions = await account.a_get_positions(session)
86
- print(positions[0])
87
- ```
88
-
89
76
  ## Placing an order
90
77
 
91
78
  ```python
@@ -94,8 +81,8 @@ from tastytrade import Account
94
81
  from tastytrade.instruments import Equity
95
82
  from tastytrade.order import NewOrder, OrderAction, OrderTimeInForce, OrderType
96
83
 
97
- account = Account.get(session, '5WX01234')
98
- symbol = Equity.get(session, 'USO')
84
+ account = await Account.get(session, '5WX01234')
85
+ symbol = await Equity.get(session, 'USO')
99
86
  leg = symbol.build_leg(Decimal('5'), OrderAction.BUY_TO_OPEN) # buy to open 5 shares
100
87
 
101
88
  order = NewOrder(
@@ -104,7 +91,7 @@ order = NewOrder(
104
91
  legs=[leg], # you can have multiple legs in an order
105
92
  price=Decimal('-10') # limit price, $10/share debit for a total value of $50
106
93
  )
107
- response = account.place_order(session, order, dry_run=True) # a test order
94
+ response = await account.place_order(session, order, dry_run=True) # a test order
108
95
  print(response)
109
96
  ```
110
97
 
@@ -120,7 +107,7 @@ from tastytrade.dxfeed import Greeks
120
107
  from tastytrade.instruments import get_option_chain
121
108
  from tastytrade.utils import get_tasty_monthly
122
109
 
123
- chain = get_option_chain(session, 'SPLG')
110
+ chain = await get_option_chain(session, 'SPLG')
124
111
  exp = get_tasty_monthly() # 45 DTE expiration!
125
112
  subs_list = [chain[exp][0].streamer_symbol]
126
113
 
@@ -0,0 +1,59 @@
1
+ Account Streamer
2
+ ================
3
+
4
+ Basic usage
5
+ -----------
6
+
7
+ The account streamer is used to track account-level updates, such as order fills, watchlist updates and quote alerts.
8
+ Typically, you'll want a separate task running for the account streamer, which can then notify your application about important events.
9
+
10
+ Here's an example of setting up an account streamer to continuously wait for events and print them:
11
+
12
+ .. code-block:: python
13
+
14
+ from tastytrade import Account, AlertStreamer, Watchlist
15
+
16
+ async with AlertStreamer(session) as streamer:
17
+ # changes in public watchlists
18
+ await streamer.subscribe_public_watchlists()
19
+ # quote alerts configured by the user
20
+ await streamer.subscribe_quote_alerts()
21
+
22
+ async for wl in streamer.listen(Watchlist):
23
+ print(wl)
24
+
25
+ Probably the most important information the account streamer handles is order fills. We can listen just for orders like so:
26
+
27
+ .. code-block:: python
28
+
29
+ from tastytrade.order import PlacedOrder
30
+
31
+ async with AlertStreamer(session) as streamer:
32
+ # updates to balances, orders, and positions
33
+ accounts = await Account.get(session)
34
+ await streamer.subscribe_accounts(accounts)
35
+
36
+ async for order in streamer.listen(PlacedOrder):
37
+ print(order)
38
+
39
+ Auto-retries
40
+ ------------
41
+
42
+ Often, account streamer connections should be long-lived and persistent. While the SDK doesn't provide this functionality itself, it's pretty trivial to build with just a few lines of code:
43
+
44
+ .. code-block:: python
45
+
46
+ from anyio import sleep
47
+ from httpx_ws import HTTPXWSException
48
+
49
+ tries, max_tries = 0, 3
50
+ while (tries := tries + 1) <= max_tries:
51
+ try:
52
+ async with AlertStreamer(session) as streamer:
53
+ print("Yay! Connected!")
54
+ ...
55
+ except* HTTPXWSException:
56
+ print("Oh no! Disconnected!")
57
+ await sleep(tries ** 2)
58
+
59
+ Now we have persistence, exponential backoff, and disconnect/reconnect hooks--easy!
@@ -8,19 +8,19 @@ The easiest way to get an account is to grab all accounts associated with a spec
8
8
  .. code-block:: python
9
9
 
10
10
  from tastytrade import Account
11
- accounts = Account.get(session)
11
+ accounts = await Account.get(session)
12
12
 
13
13
  You can also get a specific account by its unique ID:
14
14
 
15
15
  .. code-block:: python
16
16
 
17
- account = Account.get(session, '5WX01234')
17
+ account = await Account.get(session, '5WX01234')
18
18
 
19
19
  The ``get_balances`` function can be used to obtain information about the current buying power and cash balance:
20
20
 
21
21
  .. code-block:: python
22
22
 
23
- balance = account.get_balances(session)
23
+ balance = await account.get_balances(session)
24
24
  print(balance)
25
25
 
26
26
  >>> AccountBalance(account_number='5WX01234', cash_balance=Decimal('87.055'), long_equity_value=Decimal('4046.05'), short_equity_value=Decimal('0.0'), long_derivative_value=Decimal('0.0'), short_derivative_value=Decimal('0.0'), long_futures_value=Decimal('0.0'), short_futures_value=Decimal('0.0'), long_futures_derivative_value=Decimal('0.0'), short_futures_derivative_value=Decimal('0.0'), long_margineable_value=Decimal('0.0'), short_margineable_value=Decimal('0.0'), margin_equity=Decimal('4133.105'), equity_buying_power=Decimal('87.055'), derivative_buying_power=Decimal('87.055'), day_trading_buying_power=Decimal('0.0'), futures_margin_requirement=Decimal('0.0'), available_trading_funds=Decimal('0.0'), maintenance_requirement=Decimal('4048.85'), maintenance_call_value=Decimal('0.0'), reg_t_call_value=Decimal('0.0'), day_trading_call_value=Decimal('0.0'), day_equity_call_value=Decimal('0.0'), net_liquidating_value=Decimal('4133.105'), cash_available_to_withdraw=Decimal('87.06'), day_trade_excess=Decimal('87.06'), pending_cash=Decimal('0.0'), pending_cash_effect=<PriceEffect.NONE: 'None'>, long_cryptocurrency_value=Decimal('0.0'), short_cryptocurrency_value=Decimal('0.0'), cryptocurrency_margin_requirement=Decimal('0.0'), unsettled_cryptocurrency_fiat_amount=Decimal('0.0'), unsettled_cryptocurrency_fiat_effect=<PriceEffect.NONE: 'None'>, closed_loop_available_balance=Decimal('87.06'), equity_offering_margin_requirement=Decimal('0.0'), long_bond_value=Decimal('0.0'), bond_margin_requirement=Decimal('0.0'), snapshot_date=datetime.date(2023, 11, 28), reg_t_margin_requirement=Decimal('4048.85'), futures_overnight_margin_requirement=Decimal('0.0'), futures_intraday_margin_requirement=Decimal('0.0'), maintenance_excess=Decimal('87.055'), pending_margin_interest=Decimal('0.0'), effective_cryptocurrency_buying_power=Decimal('87.055'), updated_at=datetime.datetime(2023, 11, 28, 20, 54, 33, 556000, tzinfo=datetime.timezone.utc), apex_starting_day_margin_equity=None, buying_power_adjustment=None, buying_power_adjustment_effect=None, time_of_day=None)
@@ -29,7 +29,7 @@ To obtain information about current positions:
29
29
 
30
30
  .. code-block:: python
31
31
 
32
- positions = account.get_positions(session)
32
+ positions = await account.get_positions(session)
33
33
  print(positions[0])
34
34
 
35
35
  >>> CurrentPosition(account_number='5WX01234', symbol='BRK/B', instrument_type=<InstrumentType.EQUITY: 'Equity'>, underlying_symbol='BRK/B', quantity=Decimal('10'), quantity_direction='Long', close_price=Decimal('361.34'), average_open_price=Decimal('339.63'), multiplier=1, cost_effect='Credit', is_suppressed=False, is_frozen=False, realized_day_gain=Decimal('18.5'), realized_today=Decimal('279.15'), created_at=datetime.datetime(2023, 3, 31, 14, 35, 40, 138000, tzinfo=datetime.timezone.utc), updated_at=datetime.datetime(2023, 8, 10, 15, 42, 7, 482000, tzinfo=datetime.timezone.utc), mark=None, mark_price=None, restricted_quantity=Decimal('0'), expires_at=None, fixing_price=None, deliverable_type=None, average_yearly_market_close_price=Decimal('339.63'), average_daily_market_close_price=Decimal('361.34'), realized_day_gain_effect=<PriceEffect.CREDIT: 'Credit'>, realized_day_gain_date=datetime.date(2023, 8, 10), realized_today_effect=<PriceEffect.CREDIT: 'Credit'>, realized_today_date=datetime.date(2023, 8, 10))
@@ -38,7 +38,7 @@ To fetch a list of past transactions:
38
38
 
39
39
  .. code-block:: python
40
40
 
41
- history = account.get_history(session, start_date=date(2024, 1, 1))
41
+ history = await account.get_history(session, start_date=date(2024, 1, 1))
42
42
  print(history[-1])
43
43
 
44
44
  >>> Transaction(id=280070508, account_number='5WX01234', transaction_type='Trade', transaction_sub_type='Sell to Close', description='Sold 10 BRK/B @ 384.04', executed_at=datetime.datetime(2024, 1, 26, 15, 51, 53, 685000, tzinfo=datetime.timezone.utc), transaction_date=datetime.date(2024, 1, 26), value=Decimal('3840.4'), value_effect=<PriceEffect.CREDIT: 'Credit'>, net_value=Decimal('3840.35'), net_value_effect=<PriceEffect.CREDIT: 'Credit'>, is_estimated_fee=True, symbol='BRK/B', instrument_type=<InstrumentType.EQUITY: 'Equity'>, underlying_symbol='BRK/B', action='Sell to Close', quantity=Decimal('10.0'), price=Decimal('384.04'), regulatory_fees=Decimal('0.042'), regulatory_fees_effect=<PriceEffect.DEBIT: 'Debit'>, clearing_fees=Decimal('0.008'), clearing_fees_effect=<PriceEffect.DEBIT: 'Debit'>, commission=Decimal('0.0'), commission_effect=<PriceEffect.NONE: 'None'>, proprietary_index_option_fees=Decimal('0.0'), proprietary_index_option_fees_effect=<PriceEffect.NONE: 'None'>, ext_exchange_order_number='12271026815307', ext_global_order_number=2857, ext_group_id='0', ext_group_fill_id='0', ext_exec_id='0', exec_id='123_40126000126350300000', exchange='JNS', order_id=305250635, exchange_affiliation_identifier='', leg_count=1, destination_venue='JANE_STREET_EQUITIES_A', other_charge=None, other_charge_effect=None, other_charge_description=None, reverses_id=None, cost_basis_reconciliation_date=None, lots=None, agency_price=None, principal_price=None)
@@ -48,7 +48,7 @@ We can also view portfolio P/L over time (and even plot it!):
48
48
  .. code-block:: python
49
49
 
50
50
  import matplotlib.pyplot as plt
51
- nl = account.get_net_liquidating_value_history(session, time_back='1m') # past 1 month
51
+ nl = await account.get_net_liquidating_value_history(session, time_back='1m') # past 1 month
52
52
  plt.plot([n.time for n in nl], [n.close for n in nl])
53
53
  plt.show()
54
54
 
@@ -0,0 +1,73 @@
1
+ Data Streamer
2
+ =============
3
+
4
+ Basic usage
5
+ -----------
6
+
7
+ The streamer is a websocket connection to dxfeed (the Tastytrade data provider) that allows you to subscribe to real-time data for quotes, greeks, and more.
8
+ You can create a streamer using an active production session:
9
+
10
+ .. code-block:: python
11
+
12
+ from tastytrade import DXLinkStreamer
13
+ async with DXLinkStreamer(session) as streamer:
14
+ pass
15
+
16
+ Once you've created the streamer, you can subscribe/unsubscribe to events, like ``Quote``:
17
+
18
+ .. code-block:: python
19
+
20
+ from tastytrade.dxfeed import Quote
21
+ subs_list = ['SPY'] # you can add more symbols here!
22
+
23
+ async with DXLinkStreamer(session) as streamer:
24
+ await streamer.subscribe(Quote, subs_list)
25
+ quotes = {}
26
+ async for quote in streamer.listen(Quote):
27
+ quotes[quote.event_symbol] = quote
28
+ if len(quotes) >= len(subs_list):
29
+ break
30
+ print(quotes)
31
+
32
+ >>> [{'SPY': Quote(event_symbol='SPY', event_time=0, sequence=0, time_nano_part=0, bid_time=0, bid_exchange_code='Q', bid_price=411.58, bid_size=400.0, ask_time=0, ask_exchange_code='Q', ask_price=411.6, ask_size=1313.0), 'SPX': Quote(event_symbol='SPX', event_time=0, sequence=0, time_nano_part=0, bid_time=0, bid_exchange_code='\x00', bid_price=4122.49, bid_size='NaN', ask_time=0, ask_exchange_code='\x00', ask_price=4123.65, ask_size='NaN')}]
33
+
34
+ We can also use the streamer to stream greeks for options symbols:
35
+
36
+ .. code-block:: python
37
+
38
+ from tastytrade.dxfeed import Greeks
39
+ from tastytrade.instruments import get_option_chain
40
+ from tastytrade.utils import get_tasty_monthly
41
+
42
+ chain = await get_option_chain(session, 'SPLG')
43
+ exp = get_tasty_monthly() # 45 DTE expiration!
44
+ subs_list = [chain[exp][0].streamer_symbol]
45
+
46
+ async with DXLinkStreamer(session) as streamer:
47
+ await streamer.subscribe(Greeks, subs_list)
48
+ greeks = await streamer.get_event(Greeks)
49
+ print(greeks)
50
+
51
+ >>> Greeks(event_symbol='.SPLG230616C23', event_time=0, event_flags=0, index=7235129486797176832, time=1684559855338, sequence=0, price=26.3380972233688, volatility=0.396983376650804, delta=0.999999999996191, gamma=4.81989763184255e-12, theta=-2.5212017514875e-12, rho=0.01834504287973133, vega=3.7003015672215e-12)
52
+
53
+ Auto-retries
54
+ ------------
55
+
56
+ Often, streamer connections should be long-lived and persistent. While the SDK doesn't provide this functionality itself, it's pretty trivial to build with just a few lines of code:
57
+
58
+ .. code-block:: python
59
+
60
+ from anyio import sleep
61
+ from httpx_ws import HTTPXWSException
62
+
63
+ tries, max_tries = 0, 3
64
+ while (tries := tries + 1) <= max_tries:
65
+ try:
66
+ async with DXLinkStreamer(session) as streamer:
67
+ print("Yay! Connected!")
68
+ ...
69
+ except* HTTPXWSException:
70
+ print("Oh no! Disconnected!")
71
+ await sleep(tries ** 2)
72
+
73
+ Now we have persistence, exponential backoff, and disconnect/reconnect hooks--easy!
@@ -17,7 +17,7 @@
17
17
  Tastytrade Python SDK
18
18
  =====================
19
19
 
20
- A simple, reverse-engineered, sync/async SDK for Tastytrade built on their (now mostly public) API. This will allow you to create trading algorithms for whatever strategies you may have quickly and painlessly in Python.
20
+ A typed, async SDK for Tastytrade built on their public API. This will allow you to create trading algorithms for whatever strategies you may have quickly and painlessly in Python.
21
21
 
22
22
  .. tip::
23
23
  Want to see the SDK in action? Check out `tastytrade-cli <https://github.com/tastyware/tastytrade-cli>`_, a CLI for Tastytrade that showcases many of the SDK's features.
@@ -32,7 +32,6 @@ A simple, reverse-engineered, sync/async SDK for Tastytrade built on their (now
32
32
 
33
33
  installation
34
34
  sessions
35
- sync-async
36
35
  accounts
37
36
  instruments
38
37
  orders
@@ -22,9 +22,9 @@ Note that ETFs and indices are treated as equities for the purposes of the API.
22
22
 
23
23
  from tastytrade.instruments import Equity, FutureOption
24
24
 
25
- equities = Equity.get(session, ['SPY', 'AAPL'])
25
+ equities = await Equity.get(session, ['SPY', 'AAPL'])
26
26
  print(equities[0].is_etf, equities[0].description)
27
- future_option = FutureOption.get(session, './GCJ4 OG4G4 240223P1915')
27
+ future_option = await FutureOption.get(session, './GCJ4 OG4G4 240223P1915')
28
28
  print(future_option.exchange)
29
29
 
30
30
  >>> (False, 'APPLE INC')
@@ -42,10 +42,10 @@ The symbol structure for options and futures options is somewhat complex, so you
42
42
  from tastytrade.instruments import get_option_chain, get_future_option_chain
43
43
  from tastytrade.utils import get_tasty_monthly
44
44
 
45
- chain = get_option_chain(session, 'SPLG')
45
+ chain = await get_option_chain(session, 'SPLG')
46
46
  exp = get_tasty_monthly() # 45 DTE expiration!
47
47
  print(chain[exp][0])
48
- future_chain = get_future_option_chain(session, '/MCL')
48
+ future_chain = await get_future_option_chain(session, '/MCL')
49
49
  print(future_chain.keys()) # print all expirations
50
50
 
51
51
  >>> instrument_type=<InstrumentType.EQUITY_OPTION: 'Equity Option'> symbol='SPLG 240315C00024000' active=True strike_price=Decimal('24.0') root_symbol='SPLG' underlying_symbol='SPLG' expiration_date=datetime.date(2024, 3, 15) exercise_style='American' shares_per_contract=100 option_type=<OptionType.CALL: 'C'> option_chain_type='Standard' expiration_type='Regular' settlement_type='PM' stops_trading_at=datetime.datetime(2024, 3, 15, 20, 0, tzinfo=datetime.timezone.utc) market_time_instrument_collection='Equity Option' days_to_expiration=38 expires_at=datetime.datetime(2024, 3, 15, 20, 0, tzinfo=datetime.timezone.utc) is_closing_only=False listed_market=None halted_at=None old_security_number=None streamer_symbol='.SPLG240315C24'
@@ -57,12 +57,12 @@ Alternatively, ``NestedOptionChain`` and ``NestedFutureOptionChain`` provide a s
57
57
 
58
58
  from tastytrade.instruments import NestedOptionChain
59
59
 
60
- chain = NestedOptionChain.get(session, 'SPY')
60
+ chain = await NestedOptionChain.get(session, 'SPY')
61
61
  print(chain.expirations[0].strikes[0])
62
62
 
63
63
  >>> Strike(strike_price=Decimal('437.0'), call='SPY 240417C00437000', put='SPY 240417P00437000', call_streamer_symbol='.SPY240417C437', put_streamer_symbol='.SPY240417P437')
64
64
 
65
- Each expiration contains a list of these strikes, which have the associated put and call symbols that can then be used to fetch option objects via ``Option.get_options()`` or converted to dxfeed symbols for use with the streamer via ``Option.occ_to_streamer_symbol()``.
65
+ Each expiration contains a list of these strikes, which have the associated put and call symbols that can then be used to fetch option objects via ``Option.get_options()``, as well as dxfeed symbols for use with the streamer.
66
66
 
67
67
  Placing trades
68
68
  --------------
@@ -76,7 +76,7 @@ This makes placing new trades across a wide variety of instruments surprisingly
76
76
  from tastytrade.order import *
77
77
  from datetime import date
78
78
 
79
- chain = get_future_option_chain(session, '/MCL')
79
+ chain = await get_future_option_chain(session, '/MCL')
80
80
  put = chain[date(2024, 3, 15)][286]
81
81
  call = chain[date(2024, 3, 15)][187]
82
82
 
@@ -85,13 +85,13 @@ This makes placing new trades across a wide variety of instruments surprisingly
85
85
  order_type=OrderType.LIMIT,
86
86
  legs=[
87
87
  # two parameters: quantity and order action
88
- put.build_leg(Decimal(1), OrderAction.SELL_TO_OPEN),
89
- call.build_leg(Decimal(1), OrderAction.SELL_TO_OPEN)
88
+ put.build_leg(1, OrderAction.SELL_TO_OPEN),
89
+ call.build_leg(1, OrderAction.SELL_TO_OPEN)
90
90
  ],
91
91
  price=Decimal('1.25') # price is always per quantity, not total
92
92
  )
93
93
  # assuming an initialized account
94
- account.place_order(session, order, dry_run=False)
94
+ await account.place_order(session, order, dry_run=False)
95
95
 
96
96
  That's it! We just sold a micro crude oil futures strangle in a few lines of code.
97
97
  Note that price is per quantity, not the price for the entire order! So if the legs looked like this:
@@ -99,8 +99,8 @@ Note that price is per quantity, not the price for the entire order! So if the l
99
99
  .. code-block:: python
100
100
 
101
101
  legs=[
102
- put.build_leg(Decimal(2), OrderAction.SELL_TO_OPEN),
103
- call.build_leg(Decimal(2), OrderAction.SELL_TO_OPEN)
102
+ put.build_leg(2, OrderAction.SELL_TO_OPEN),
103
+ call.build_leg(2, OrderAction.SELL_TO_OPEN)
104
104
  ]
105
105
 
106
106
  the price would still be ``Decimal('1.25')``, and the total credit collected would be $2.50. This holds true for ratio spreads, so a 4:2 ratio spread should be priced as a 2:1 ratio spread.
@@ -10,7 +10,7 @@ Fortunately, Tastytrade has endpoints that make fetching data for one-time use a
10
10
  from tastytrade.market_data import get_market_data
11
11
  from tastytrade.order import InstrumentType
12
12
 
13
- data = get_market_data(session, "SPY", InstrumentType.EQUITY)
13
+ data = await get_market_data(session, "SPY", InstrumentType.EQUITY)
14
14
  print(data)
15
15
 
16
16
  >>> symbol='SPY' instrument_type=<InstrumentType.EQUITY: 'Equity'> updated_at=datetime.datetime(2025, 4, 28, 21, 46, 48, 84000, tzinfo=TzInfo(UTC)) bid_size=Decimal('2.0') ask_size=Decimal('4.0') mark=Decimal('549.96') close_price_type=<ClosePriceType.FINAL: 'Final'> prev_close=Decimal('550.64') prev_close_price_type=<ClosePriceType.FINAL: 'Final'> summary_date=datetime.date(2025, 4, 28) prev_close_date=datetime.date(2025, 4, 25) halt_start_time=-1 halt_end_time=-1 ask=Decimal('550.0') beta=Decimal('1.009463163') bid=Decimal('549.9') close=Decimal('550.85') day_high_price=Decimal('553.55') day_low_price=Decimal('545.02') dividend_amount=Decimal('1.695528') dividend_frequency=Decimal('4.0') high_limit_price=Decimal('606.41') last=Decimal('549.96') last_mkt=Decimal('550.85') low_limit_price=Decimal('496.16') mid=Decimal('549.95') open=Decimal('551.39') volume=Decimal('47417792.0') year_low_price=Decimal('481.8') year_high_price=Decimal('613.23')
@@ -21,7 +21,7 @@ You can also fetch data for multiple symbols at once:
21
21
 
22
22
  from tastytrade.market_data import get_market_data_by_type
23
23
 
24
- data = get_market_data_by_type(
24
+ data = await get_market_data_by_type(
25
25
  session,
26
26
  indices=["SPX", "VIX"],
27
27
  cryptocurrencies=["ETH/USD", "BTC/USD"],
@@ -10,7 +10,7 @@ The ``get_market_sessions`` function can be used to obtain information about the
10
10
  .. code-block:: python
11
11
 
12
12
  from tastytrade.market_sessions import ExchangeType, get_market_sessions
13
- get_market_sessions(session, exchanges=[ExchangeType.NYSE])
13
+ await get_market_sessions(session, exchanges=[ExchangeType.NYSE])
14
14
 
15
15
  >>> [MarketSession(close_at=None, close_at_ext=None, instrument_collection='Equity', open_at=None, start_at=None, next_session=MarketSessionSnapshot(close_at=datetime.datetime(2025, 2, 18, 21, 0, tzinfo=TzInfo(UTC)), close_at_ext=datetime.datetime(2025, 2, 19, 1, 0, tzinfo=TzInfo(UTC)), instrument_collection='Equity', open_at=datetime.datetime(2025, 2, 18, 14, 30, tzinfo=TzInfo(UTC)), session_date=datetime.date(2025, 2, 18), start_at=datetime.datetime(2025, 2, 18, 13, 15, tzinfo=TzInfo(UTC))), previous_session=MarketSessionSnapshot(close_at=datetime.datetime(2025, 2, 14, 21, 0, tzinfo=TzInfo(UTC)), close_at_ext=datetime.datetime(2025, 2, 15, 1, 0, tzinfo=TzInfo(UTC)), instrument_collection='Equity', open_at=datetime.datetime(2025, 2, 14, 14, 30, tzinfo=TzInfo(UTC)), session_date=datetime.date(2025, 2, 14), start_at=datetime.datetime(2025, 2, 14, 13, 15, tzinfo=TzInfo(UTC))), status=<MarketStatus.CLOSED: 'Closed'>)]
16
16
 
@@ -19,7 +19,7 @@ The ``get_market_holidays`` function can be used to obtain information about mar
19
19
  .. code-block:: python
20
20
 
21
21
  from tastytrade.market_sessions import get_market_holidays
22
- calendar = Market.get_market_holidays(session)
22
+ calendar = await Market.get_market_holidays(session)
23
23
  print(calendar.half_days)
24
24
  print(calendar.holidays)
25
25
 
@@ -32,7 +32,7 @@ I case you only want to extract the market status, this is one way to do it:
32
32
 
33
33
  from tastytrade.market_sessions import ExchangeType, MarketStatus, get_market_sessions
34
34
 
35
- market_sessions = get_market_sessions(session, exchanges=[ExchangeType.NYSE, ExchangeType.CME])
35
+ market_sessions = await get_market_sessions(session, exchanges=[ExchangeType.NYSE, ExchangeType.CME])
36
36
  print([ms.status != MarketStatus.CLOSED for ms in market_sessions])
37
37
 
38
38
  >>> [False, False]