tastytrade 11.0.3__tar.gz → 11.0.5__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 (82) hide show
  1. {tastytrade-11.0.3 → tastytrade-11.0.5}/PKG-INFO +1 -1
  2. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/sessions.rst +0 -9
  3. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/__init__.py +1 -1
  4. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/account.py +0 -2
  5. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/event.py +0 -2
  6. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/instruments.py +1 -1
  7. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/order.py +6 -4
  8. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/session.py +12 -27
  9. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/utils.py +0 -24
  10. {tastytrade-11.0.3 → tastytrade-11.0.5}/uv.lock +5 -3
  11. tastytrade-11.0.3/tastytrade/oauth.py +0 -129
  12. tastytrade-11.0.3/tmp.py +0 -32
  13. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/CONTRIBUTING.md +0 -0
  14. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/FUNDING.yml +0 -0
  15. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/pull_request_template.md +0 -0
  16. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/workflows/python-app.yml +0 -0
  17. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/workflows/python-publish-test.yml +0 -0
  18. {tastytrade-11.0.3 → tastytrade-11.0.5}/.github/workflows/python-publish.yml +0 -0
  19. {tastytrade-11.0.3 → tastytrade-11.0.5}/.gitignore +0 -0
  20. {tastytrade-11.0.3 → tastytrade-11.0.5}/.python-version +0 -0
  21. {tastytrade-11.0.3 → tastytrade-11.0.5}/.readthedocs.yaml +0 -0
  22. {tastytrade-11.0.3 → tastytrade-11.0.5}/LICENSE +0 -0
  23. {tastytrade-11.0.3 → tastytrade-11.0.5}/Makefile +0 -0
  24. {tastytrade-11.0.3 → tastytrade-11.0.5}/README.md +0 -0
  25. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/Makefile +0 -0
  26. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/account-streamer.rst +0 -0
  27. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/accounts.rst +0 -0
  28. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/account.rst +0 -0
  29. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/dxfeed.rst +0 -0
  30. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/instruments.rst +0 -0
  31. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/market-data.rst +0 -0
  32. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/market-sessions.rst +0 -0
  33. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/metrics.rst +0 -0
  34. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/order.rst +0 -0
  35. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/search.rst +0 -0
  36. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/session.rst +0 -0
  37. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/streamer.rst +0 -0
  38. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/utils.rst +0 -0
  39. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/api/watchlists.rst +0 -0
  40. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/conf.py +0 -0
  41. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/data-streamer.rst +0 -0
  42. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/img/netliq.png +0 -0
  43. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/index.rst +0 -0
  44. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/installation.rst +0 -0
  45. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/instruments.rst +0 -0
  46. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/make.bat +0 -0
  47. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/market-data.rst +0 -0
  48. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/market-sessions.rst +0 -0
  49. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/orders.rst +0 -0
  50. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/sync-async.rst +0 -0
  51. {tastytrade-11.0.3 → tastytrade-11.0.5}/docs/watchlists.rst +0 -0
  52. {tastytrade-11.0.3 → tastytrade-11.0.5}/pyproject.toml +0 -0
  53. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/__init__.py +0 -0
  54. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/candle.py +0 -0
  55. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/greeks.py +0 -0
  56. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/profile.py +0 -0
  57. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/quote.py +0 -0
  58. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/summary.py +0 -0
  59. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/theoprice.py +0 -0
  60. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/timeandsale.py +0 -0
  61. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/trade.py +0 -0
  62. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/dxfeed/underlying.py +0 -0
  63. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/market_data.py +0 -0
  64. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/market_sessions.py +0 -0
  65. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/metrics.py +0 -0
  66. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/py.typed +0 -0
  67. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/search.py +0 -0
  68. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/streamer.py +0 -0
  69. {tastytrade-11.0.3 → tastytrade-11.0.5}/tastytrade/watchlists.py +0 -0
  70. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/__init__.py +0 -0
  71. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/conftest.py +0 -0
  72. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_account.py +0 -0
  73. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_dxfeed.py +0 -0
  74. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_instruments.py +0 -0
  75. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_market_data.py +0 -0
  76. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_market_sessions.py +0 -0
  77. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_metrics.py +0 -0
  78. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_search.py +0 -0
  79. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_session.py +0 -0
  80. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_streamer.py +0 -0
  81. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_utils.py +0 -0
  82. {tastytrade-11.0.3 → tastytrade-11.0.5}/tests/test_watchlists.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tastytrade
3
- Version: 11.0.3
3
+ Version: 11.0.5
4
4
  Summary: An unofficial, sync/async SDK for Tastytrade!
5
5
  Project-URL: Homepage, https://github.com/tastyware/tastytrade
6
6
  Project-URL: Documentation, https://tastyworks-api.rtfd.io
@@ -13,15 +13,6 @@ Generating an initial refresh token
13
13
 
14
14
  In order to generate an initial refresh token, you have two options. The easiest way is to simply generate one from Tastytrade's website: go to OAuth Applications > Manage > Create Grant to get a new refresh token, **which you should also save**.
15
15
 
16
- The other option (which is actually the only option for sandbox accounts) is to run this helper code:
17
-
18
- .. code-block:: python
19
-
20
- from tastytrade.oauth import login
21
- login(is_test=True)
22
-
23
- This will open up a web interface in your browser where you'll be prompted to paste your client ID and client secret. These credentials will then be used to connect your application to Tastytrade. After following the steps in your browser, you should see your new refresh token in the browser and in the console.
24
-
25
16
  At this point, OAuth is now setup correctly! Doing these steps once is sufficient for **indefinite usage** of ``Session`` for authentication to the API, since refresh tokens never expire. From now on you can simply authenticate with your client secret and refresh token.
26
17
 
27
18
  Creating a session
@@ -4,7 +4,7 @@ API_URL = "https://api.tastyworks.com"
4
4
  API_VERSION = "20251101"
5
5
  CERT_URL = "https://api.cert.tastyworks.com"
6
6
  VAST_URL = "https://vast.tastyworks.com"
7
- VERSION = "11.0.3"
7
+ VERSION = "11.0.5"
8
8
 
9
9
  __version__ = VERSION
10
10
  version_str: str = f"tastyware/tastytrade:v{VERSION}"
@@ -546,8 +546,6 @@ class Account(TastytradeData):
546
546
  :param session: the session to use for the request.
547
547
  :param account_number: the account ID to get.
548
548
  :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
549
  """
552
550
  if account_number:
553
551
  data = session._get(f"/customers/me/accounts/{account_number}")
@@ -41,8 +41,6 @@ class Event(BaseModel):
41
41
  a :class:`~tastyworks.streamer.DXFeedStreamer`.
42
42
 
43
43
  :param data: list of raw quote data from streamer
44
-
45
- :return: list of event objects from data
46
44
  """
47
45
  objs: list[Event] = []
48
46
  size = len(cls.model_fields)
@@ -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
@@ -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,
@@ -254,6 +252,8 @@ class NewOrder(TastytradeData):
254
252
  preflight_id: str | None = None
255
253
  rules: OrderRule | None = None
256
254
  advanced_instructions: AdvancedInstructions | None = None
255
+ #: External identifier for the order, used to track orders across systems
256
+ external_identifier: str | None = None
257
257
 
258
258
  @computed_field # type: ignore[misc]
259
259
  @property
@@ -330,6 +330,8 @@ class PlacedOrder(TastytradeData):
330
330
  preflight_id: str | int | None = None
331
331
  order_rule: OrderRule | None = None
332
332
  source: str | None = None
333
+ #: External identifier for the order, used to track orders across systems
334
+ external_identifier: str | None = None
333
335
 
334
336
  @model_validator(mode="before")
335
337
  @classmethod
@@ -9,8 +9,8 @@ from typing_extensions import Self
9
9
 
10
10
  from tastytrade import API_URL, API_VERSION, CERT_URL, logger
11
11
  from tastytrade.utils import (
12
- TZ,
13
12
  TastytradeData,
13
+ now_in_new_york,
14
14
  validate_and_parse,
15
15
  validate_response,
16
16
  )
@@ -274,11 +274,9 @@ class Session:
274
274
  #: Refresh token for the user
275
275
  self.refresh_token = refresh_token
276
276
  # The headers to use for API requests
277
- headers = {
278
- "Accept": "application/json",
279
- "Accept-Version": API_VERSION,
280
- "Content-Type": "application/json",
281
- }
277
+ headers = {"Accept": "application/json", "Content-Type": "application/json"}
278
+ if not is_test: # not accepted in sandbox
279
+ headers["Accept-Version"] = API_VERSION
282
280
  #: httpx client for sync requests
283
281
  self.sync_client = Client(
284
282
  base_url=(CERT_URL if is_test else API_URL), headers=headers, proxy=proxy
@@ -288,7 +286,7 @@ class Session:
288
286
  base_url=self.sync_client.base_url, headers=headers, proxy=proxy
289
287
  )
290
288
  #: expiration for streamer token
291
- self.streamer_expiration = datetime.now(TZ)
289
+ self.streamer_expiration = now_in_new_york()
292
290
  self.refresh()
293
291
 
294
292
  def _streamer_refresh(self) -> None:
@@ -325,14 +323,14 @@ class Session:
325
323
  self.session_token = data["access_token"]
326
324
  token_lifetime: int = data.get("expires_in", 900)
327
325
  #: expiration for session token
328
- self.session_expiration = datetime.now(TZ) + timedelta(seconds=token_lifetime)
326
+ self.session_expiration = now_in_new_york() + timedelta(seconds=token_lifetime)
329
327
  logger.debug(f"Refreshed token, expires in {token_lifetime}ms")
330
328
  auth_headers = {"Authorization": f"Bearer {self.session_token}"}
331
329
  # update the httpx clients with the new token
332
330
  self.sync_client.headers.update(auth_headers)
333
331
  self.async_client.headers.update(auth_headers)
334
332
  # update the streamer token if necessary
335
- if self.streamer_expiration < self.session_expiration:
333
+ if not self.is_test and self.streamer_expiration < self.session_expiration:
336
334
  self._streamer_refresh()
337
335
 
338
336
  async def a_refresh(self) -> None:
@@ -357,14 +355,14 @@ class Session:
357
355
  # update the relevant tokens
358
356
  self.session_token = data["access_token"]
359
357
  token_lifetime: int = data.get("expires_in", 900)
360
- self.session_expiration = datetime.now(TZ) + timedelta(token_lifetime)
358
+ self.session_expiration = now_in_new_york() + timedelta(token_lifetime)
361
359
  logger.debug(f"Refreshed token, expires in {token_lifetime}ms")
362
360
  auth_headers = {"Authorization": f"Bearer {self.session_token}"}
363
361
  # update the httpx clients with the new token
364
362
  self.sync_client.headers.update(auth_headers)
365
363
  self.async_client.headers.update(auth_headers)
366
364
  # update the streamer token if necessary
367
- if self.streamer_expiration < self.session_expiration:
365
+ if not self.is_test and self.streamer_expiration < self.session_expiration:
368
366
  # Pull streamer tokens and urls
369
367
  data = await self._a_get("/api-quote-tokens")
370
368
  # Auth token for dxfeed websocket
@@ -378,8 +376,6 @@ class Session:
378
376
  async def a_get_customer(self) -> Customer:
379
377
  """
380
378
  Gets the customer dict from the API.
381
-
382
- :return: a Tastytrade 'Customer' object in JSON format.
383
379
  """
384
380
  data = await self._a_get("/customers/me")
385
381
  return Customer(**data)
@@ -387,8 +383,6 @@ class Session:
387
383
  def get_customer(self) -> Customer:
388
384
  """
389
385
  Gets the customer dict from the API.
390
-
391
- :return: a Tastytrade 'Customer' object in JSON format.
392
386
  """
393
387
  data = self._get("/customers/me")
394
388
  return Customer(**data)
@@ -396,8 +390,6 @@ class Session:
396
390
  async def a_validate(self) -> bool:
397
391
  """
398
392
  Validates the current session by sending a request to the API.
399
-
400
- :return: True if the session is valid and False otherwise.
401
393
  """
402
394
  response = await self.async_client.post("/sessions/validate")
403
395
  return response.status_code // 100 == 2
@@ -405,8 +397,6 @@ class Session:
405
397
  def validate(self) -> bool:
406
398
  """
407
399
  Validates the current session by sending a request to the API.
408
-
409
- :return: True if the session is valid and False otherwise.
410
400
  """
411
401
  response = self.sync_client.post("/sessions/validate")
412
402
  return response.status_code // 100 == 2
@@ -422,6 +412,7 @@ class Session:
422
412
  del attrs["sync_client"]
423
413
  attrs["session_expiration"] = self.session_expiration.strftime(_fmt)
424
414
  attrs["streamer_expiration"] = self.streamer_expiration.strftime(_fmt)
415
+ attrs["headers"] = dict(self.sync_client.headers.copy())
425
416
  return json.dumps(attrs)
426
417
 
427
418
  @classmethod
@@ -429,17 +420,11 @@ class Session:
429
420
  """
430
421
  Create a new Session object from a serialized string.
431
422
  """
432
- deserialized = json.loads(serialized)
423
+ deserialized: dict[str, Any] = json.loads(serialized)
424
+ headers = deserialized.pop("headers")
433
425
  self = cls.__new__(cls)
434
426
  self.__dict__ = deserialized
435
427
  base_url = CERT_URL if self.is_test else API_URL
436
- headers = {
437
- "Accept": "application/json",
438
- "Content-Type": "application/json",
439
- "Authorization": self.session_token
440
- if "user" in deserialized
441
- else f"Bearer {self.session_token}",
442
- }
443
428
  self.session_expiration = datetime.strptime(
444
429
  deserialized["session_expiration"], _fmt
445
430
  )
@@ -29,8 +29,6 @@ class PriceEffect(str, Enum):
29
29
  def now_in_new_york() -> datetime:
30
30
  """
31
31
  Gets the current time in the New York timezone.
32
-
33
- :return: current time as datetime
34
32
  """
35
33
  return datetime.now(TZ)
36
34
 
@@ -38,8 +36,6 @@ def now_in_new_york() -> datetime:
38
36
  def today_in_new_york() -> date:
39
37
  """
40
38
  Gets the current date in the New York timezone.
41
-
42
- :return: current date
43
39
  """
44
40
  return now_in_new_york().date()
45
41
 
@@ -50,8 +46,6 @@ def is_market_open_on(day: date | None = None) -> bool:
50
46
  during the given day.
51
47
 
52
48
  :param day: date to check. If not provided defaults to current NY date.
53
-
54
- :return: whether the market opens on given day
55
49
  """
56
50
  day = day or today_in_new_york()
57
51
  date_range = NYSE.valid_days(day, day)
@@ -64,8 +58,6 @@ def get_third_friday(day: date | None = None) -> date:
64
58
  or the monthly expiration associated with today's month.
65
59
 
66
60
  :param day: date to check. If not provided defaults to current NY date.
67
-
68
- :return: the associated monthly
69
61
  """
70
62
  day = (day or today_in_new_york()).replace(day=1) + timedelta(weeks=2)
71
63
  while day.weekday() != 4: # Friday
@@ -76,8 +68,6 @@ def get_third_friday(day: date | None = None) -> date:
76
68
  def get_tasty_monthly() -> date:
77
69
  """
78
70
  Gets the monthly expiration closest to 45 days from the current date.
79
-
80
- :return: the closest to 45 DTE monthly expiration
81
71
  """
82
72
  day = today_in_new_york()
83
73
  exp1 = get_third_friday(day + timedelta(weeks=4))
@@ -101,8 +91,6 @@ def get_future_fx_monthly(day: date | None = None) -> date:
101
91
  Wednesday.
102
92
 
103
93
  :param day: date to check. If not provided defaults to current NY date.
104
-
105
- :return: the associated monthly
106
94
  """
107
95
  day = (day or today_in_new_york()).replace(day=1) + timedelta(weeks=1)
108
96
  while day.weekday() != 2: # Wednesday
@@ -120,8 +108,6 @@ def get_future_treasury_monthly(day: date | None = None) -> date:
120
108
  business day prior.
121
109
 
122
110
  :param day: date to check. If not provided defaults to current NY date.
123
-
124
- :return: the associated monthly
125
111
  """
126
112
  day = day or today_in_new_york()
127
113
  last_day = _get_last_day_of_month(day)
@@ -143,8 +129,6 @@ def get_future_metal_monthly(day: date | None = None) -> date:
143
129
  which case they expire on the prior business day.
144
130
 
145
131
  :param day: date to check. If not provided defaults to current NY date.
146
-
147
- :return: the associated monthly
148
132
  """
149
133
  day = day or today_in_new_york()
150
134
  last_day = _get_last_day_of_month(day)
@@ -164,8 +148,6 @@ def get_future_grain_monthly(day: date | None = None) -> date:
164
148
  least 2 business days, the last business day of the month.
165
149
 
166
150
  :param day: date to check. If not provided defaults to current NY date.
167
-
168
- :return: the associated monthly
169
151
  """
170
152
  day = day or today_in_new_york()
171
153
  last_day = _get_last_day_of_month(day)
@@ -185,8 +167,6 @@ def get_future_oil_monthly(day: date | None = None) -> date:
185
167
  they expire 7 business days prior to the 25th day of the month.
186
168
 
187
169
  :param day: date to check. If not provided defaults to current NY date.
188
-
189
- :return: the associated monthly
190
170
  """
191
171
  last_day = (day or today_in_new_york()).replace(day=25)
192
172
  first_day = last_day.replace(day=1)
@@ -201,8 +181,6 @@ def get_future_index_monthly(day: date | None = None) -> date:
201
181
  month.
202
182
 
203
183
  :param day: date to check. If not provided defaults to current NY date.
204
-
205
- :return: the associated monthly
206
184
  """
207
185
  day = day or today_in_new_york()
208
186
  last_day = _get_last_day_of_month(day)
@@ -224,8 +202,6 @@ def _dasherize(s: str) -> str:
224
202
  Converts a string from snake case to dasherized.
225
203
 
226
204
  :param s: string to convert
227
-
228
- :return: dasherized string
229
205
  """
230
206
  return s.replace("_", "-")
231
207
 
@@ -1368,15 +1368,15 @@ wheels = [
1368
1368
 
1369
1369
  [[package]]
1370
1370
  name = "pyright"
1371
- version = "1.1.406"
1371
+ version = "1.1.407"
1372
1372
  source = { registry = "https://pypi.org/simple" }
1373
1373
  dependencies = [
1374
1374
  { name = "nodeenv" },
1375
1375
  { name = "typing-extensions" },
1376
1376
  ]
1377
- sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" }
1377
+ sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" }
1378
1378
  wheels = [
1379
- { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" },
1379
+ { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" },
1380
1380
  ]
1381
1381
 
1382
1382
  [[package]]
@@ -1549,6 +1549,8 @@ wheels = [
1549
1549
  { url = "https://files.pythonhosted.org/packages/42/cd/85b422d24ee2096eaf6faa360c95ef9bdb59097d19b9624cebce4dd9bc2a/ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad", size = 725028, upload-time = "2025-09-22T19:51:19.782Z" },
1550
1550
  { url = "https://files.pythonhosted.org/packages/4d/ac/99e6e0ea2584f84f447069d0187fe411e9b5deb7e3ddecda25001cfc7a95/ruamel.yaml.clib-0.2.14-cp39-cp39-win32.whl", hash = "sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352", size = 100915, upload-time = "2025-09-22T19:51:21.764Z" },
1551
1551
  { url = "https://files.pythonhosted.org/packages/5d/8d/846e43369658958c99d959bb7774136fff9210f9017d91a4277818ceafbf/ruamel.yaml.clib-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6", size = 118706, upload-time = "2025-09-22T19:51:20.878Z" },
1552
+ { url = "https://files.pythonhosted.org/packages/e7/cd/150fdb96b8fab27fe08d8a59fe67554568727981806e6bc2677a16081ec7/ruamel_yaml_clib-0.2.14-cp314-cp314-win32.whl", hash = "sha256:9b4104bf43ca0cd4e6f738cb86326a3b2f6eef00f417bd1e7efb7bdffe74c539", size = 102394, upload-time = "2025-11-14T21:57:36.703Z" },
1553
+ { url = "https://files.pythonhosted.org/packages/bd/e6/a3fa40084558c7e1dc9546385f22a93949c890a8b2e445b2ba43935f51da/ruamel_yaml_clib-0.2.14-cp314-cp314-win_amd64.whl", hash = "sha256:13997d7d354a9890ea1ec5937a219817464e5cc344805b37671562a401ca3008", size = 122673, upload-time = "2025-11-14T21:57:38.177Z" },
1552
1554
  ]
1553
1555
 
1554
1556
  [[package]]
@@ -1,129 +0,0 @@
1
- import re
2
- import webbrowser
3
- from http.server import BaseHTTPRequestHandler, HTTPServer
4
- from urllib.parse import parse_qs
5
-
6
- import httpx
7
-
8
- PORT = 8000
9
- REDIRECT_URI = f"http://localhost:{PORT}"
10
- SCOPES = ["read", "trade", "openid"]
11
-
12
- authorize_url = "https://my.tastytrade.com/auth.html"
13
- token_url = "https://api.tastyworks.com/oauth/token"
14
- client_id = ""
15
- client_secret = ""
16
-
17
-
18
- root_page = """
19
- <!DOCTYPE html>
20
- <html lang="en-us">
21
- <head>
22
- <meta charset="utf-8">
23
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
- <title>OAuth Setup</title>
25
- <!-- Favicon -->
26
- <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
27
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
28
- rel="stylesheet"
29
- integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
30
- crossorigin="anonymous">
31
- </head>
32
- <body>
33
- <div class="container position-absolute top-50 start-50 translate-middle"
34
- style="width: 400px">
35
- <form method="POST">
36
- <div class="row mb-3">
37
- <input type="text"
38
- required
39
- placeholder="Client ID"
40
- name="client_id"
41
- class="form-control">
42
- </div>
43
- <div class="row mb-3">
44
- <input type="password"
45
- required
46
- placeholder="Client Secret"
47
- name="client_secret"
48
- class="form-control">
49
- </div>
50
- <div class="row mb-3">
51
- <button type="submit" class="btn btn-success">Connect</button>
52
- </div>
53
- </form>
54
- </div>
55
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
56
- integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
57
- crossorigin="anonymous"></script>
58
- </body>
59
- </html>
60
- """.encode()
61
-
62
-
63
- class RequestHandler(BaseHTTPRequestHandler):
64
- def do_POST(self) -> None:
65
- global client_id, client_secret
66
- content_length = int(self.headers["Content-Length"])
67
- raw = self.rfile.read(content_length)
68
- data = parse_qs(raw.decode("utf-8"))
69
- client_id = data["client_id"][0]
70
- client_secret = data["client_secret"][0]
71
-
72
- # Redirect to login page using API key submitted by user
73
- self.send_response(302)
74
- query_string = "&".join(
75
- [
76
- "response_type=code",
77
- f"redirect_uri={REDIRECT_URI}",
78
- f"client_id={data['client_id'][0]}",
79
- f"scope={' '.join(SCOPES)}",
80
- ]
81
- )
82
- url = f"{authorize_url}?{query_string}"
83
- self.send_header("Location", url)
84
- self.end_headers()
85
-
86
- def do_GET(self) -> None:
87
- global client_id, client_secret
88
- # Serve root page with sign in link
89
- if self.path == "/":
90
- self.send_response(200)
91
- self.send_header("Content-type", "text/html; charset=utf-8")
92
- self.end_headers()
93
- self.wfile.write(root_page)
94
- else:
95
- # Check if query path contains case insensitive "code="
96
- code_match = re.search(r"code=(.+)", self.path, re.I)
97
- if code_match and client_id and client_secret:
98
- user_auth_code = code_match[1]
99
- post_data = {
100
- "grant_type": "authorization_code",
101
- "client_id": client_id,
102
- "client_secret": client_secret,
103
- "redirect_uri": REDIRECT_URI,
104
- "code": user_auth_code,
105
- }
106
- response = httpx.post(token_url, data=post_data)
107
- token_access = response.json()
108
- refresh_token: str = token_access["refresh_token"]
109
- print(refresh_token)
110
-
111
- self.send_response(200)
112
- self.send_header("Content-type", "text/html; charset=utf-8")
113
- self.end_headers()
114
- self.wfile.write(refresh_token.encode())
115
-
116
-
117
- def login(is_test: bool = False) -> None:
118
- """
119
- Starts a local HTTP server and opens the browser to OAuth login.
120
- Designed for one-time use to get a refresh token.
121
- """
122
- global authorize_url, token_url
123
- if is_test:
124
- authorize_url = "https://cert-my.staging-tasty.works/auth.html"
125
- token_url = "https://api.cert.tastyworks.com/oauth/token"
126
- httpd = HTTPServer(("", PORT), RequestHandler)
127
- print(f"Opening url: {REDIRECT_URI}")
128
- webbrowser.open(REDIRECT_URI)
129
- httpd.serve_forever()
tastytrade-11.0.3/tmp.py DELETED
@@ -1,32 +0,0 @@
1
- import asyncio
2
- import os
3
- from datetime import date, datetime, time
4
-
5
- import pandas as pd
6
-
7
- from tastytrade import Session
8
- from tastytrade.dxfeed.candle import Candle
9
- from tastytrade.streamer import DXLinkStreamer
10
- from tastytrade.utils import TZ
11
-
12
- session = Session(os.environ["TT_SECRET"], os.environ["TT_REFRESH"])
13
-
14
-
15
- async def main():
16
- async with DXLinkStreamer(session) as streamer:
17
- start_time = datetime.combine(date(2025, 11, 7), time(9, 30), tzinfo=TZ)
18
- ts = round(start_time.timestamp() * 1000)
19
- await streamer.subscribe_candle(["DIA"], "5s", start_time=start_time)
20
- candles: list[Candle] = []
21
- async for candle in streamer.listen(Candle):
22
- candles.append(candle)
23
- if candle.time <= ts:
24
- break
25
- candles.sort(key=lambda c: c.time)
26
- df = pd.DataFrame([c.model_dump() for c in candles])
27
- df = df[["time", "open", "high", "low", "close"]]
28
- print(df)
29
-
30
-
31
- if __name__ == "__main__":
32
- asyncio.run(main())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes