Skip to content

Commit

Permalink
streamers use async context managers; prod session in testing
Browse files Browse the repository at this point in the history
  • Loading branch information
Graeme22 committed Nov 17, 2023
1 parent a8a8982 commit 6490877
Show file tree
Hide file tree
Showing 16 changed files with 304 additions and 198 deletions.
13 changes: 13 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Contributions

Since Tastytrade certification sessions are severely limited in capabilities, the test suite for this SDK requires the usage of your own Tastytrade credentials. In order to run the tests, you'll need to set up your Tastytrade credentials as repository secrets on your local fork.

Secrets are protected by Github and are not visible to anyone. You can read more about repository secrets [here](https://docs.github.com/en/actions/reference/encrypted-secrets).

## Steps to follow to contribute

1. Fork the repository to your personal Github account, NOT to an organization where others may be able to indirectly access your secrets.
2. Make your changes on the forked repository.
3. Navigate to the forked repository's settings page and click on "Secrets and variables" > "Actions".
4. Click on "New repository secret" to add your Tastytrade username named `TT_USERNAME`.
5. Finally, do the same with your password, naming it `TT_PASSWORD`.
2 changes: 2 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ Fixes ...
## Pre-merge checklist
- [ ] Passing tests
- [ ] New tests added (if applicable)

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.
11 changes: 4 additions & 7 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@ jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -33,5 +30,5 @@ jobs:
run: |
python -m pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95
env:
TT_USERNAME: tastyware
TT_PASSWORD: :4s-S9/9L&Q~C]@v
TT_USERNAME: ${{ secrets.TT_USERNAME }}
TT_PASSWORD: ${{ secrets.TT_PASSWORD }}
23 changes: 11 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,19 @@ The streamer is a websocket connection to dxfeed (the Tastytrade data provider)

.. code-block:: python
from tastytrade import DXFeedStreamer
from tastytrade import DXLinkStreamer
from tastytrade.dxfeed import EventType
streamer = await DXFeedStreamer.create(session)
subs_list = ['SPY', 'SPX']
await streamer.subscribe(EventType.QUOTE, subs_list)
# this example fetches quotes once, then exits
quotes = []
async for quote in streamer.listen(EventType.QUOTE):
quotes.append(quote)
if len(quotes) >= len(subs_list):
break
print(quotes)
async with DXLinkStreamer(session) as streamer:
subs_list = ['SPY', 'GLD'] # list of symbols to subscribe to
await streamer.subscribe(EventType.QUOTE, subs_list)
# this example fetches quotes once, then exits
quotes = []
async for quote in streamer.listen(EventType.QUOTE):
quotes.append(quote)
if len(quotes) >= len(subs_list):
break
print(quotes)
>>> [Quote(eventSymbol='SPY', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='Q', bidPrice=411.58, bidSize=400.0, askTime=0, askExchangeCode='Q', askPrice=411.6, askSize=1313.0), Quote(eventSymbol='SPX', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='\x00', bidPrice=4122.49, bidSize='NaN', askTime=0, askExchangeCode='\x00', askPrice=4123.65, askSize='NaN')]

Expand Down
58 changes: 42 additions & 16 deletions docs/data-streamer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,47 @@ You can create a streamer using an active production session:

.. code-block:: python
from tastytrade import DXFeedStreamer
streamer = await DXFeedStreamer.create(session)
from tastytrade import DXLinkStreamer
streamer = await DXLinkStreamer.create(session)
Or, you can create a streamer using an asynchronous context manager:

.. code-block:: python
from tastytrade import DXLinkStreamer
async with DXLinkStreamer(session) as streamer:
pass
There are two kinds of streamers: ``DXLinkStreamer`` and ``DXFeedStreamer``. ``DXFeedStreamer`` is older, but has been kept around for compatibility reasons. It supports more event types, but it's now deprecated as it will probably be moved to delayed quotes at some point.
Once you've created the streamer, you can subscribe/unsubscribe to events, like ``Quote``:

.. code-block:: python
from tastytrade.dxfeed import EventType
subs_list = ['SPY', 'SPX']
await streamer.subscribe(EventType.QUOTE, subs_list)
quotes = []
async for quote in streamer.listen(EventType.QUOTE):
quotes.append(quote)
if len(quotes) >= len(subs_list):
break
print(quotes)
async with DXFeedStreamer(session) as streamer:
await streamer.subscribe(EventType.QUOTE, subs_list)
quotes = []
async for quote in streamer.listen(EventType.QUOTE):
quotes.append(quote)
if len(quotes) >= len(subs_list):
break
print(quotes)
>>> [Quote(eventSymbol='SPY', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='Q', bidPrice=411.58, bidSize=400.0, askTime=0, askExchangeCode='Q', askPrice=411.6, askSize=1313.0), Quote(eventSymbol='SPX', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='\x00', bidPrice=4122.49, bidSize='NaN', askTime=0, askExchangeCode='\x00', askPrice=4123.65, askSize='NaN')]

Note that these are ``asyncio`` calls, so you'll need to run this code asynchronously. Alternatively, you can do testing in a Jupyter notebook, which allows you to make async calls directly. Here's an example:

.. code-block:: python
async def main():
async with DXLinkStreamer(session) as streamer:
await streamer.subscribe(EventType.QUOTE, subs_list)
quote = await streamer.get_event(EventType.QUOTE)
print(quote)
asyncio.run(main())
>>> [Quote(eventSymbol='SPY', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='Q', bidPrice=411.58, bidSize=400.0, askTime=0, askExchangeCode='Q', askPrice=411.6, askSize=1313.0), Quote(eventSymbol='SPX', eventTime=0, sequence=0, timeNanoPart=0, bidTime=0, bidExchangeCode='\x00', bidPrice=4122.49, bidSize='NaN', askTime=0, askExchangeCode='\x00', askPrice=4123.65, askSize='NaN')]

Expand All @@ -39,13 +63,14 @@ We can also use the streamer to stream greeks for options symbols:
chain = get_option_chain(session, 'SPLG')
subs_list = [chain[date(2023, 6, 16)][0].streamer_symbol]
await streamer.subscribe(EventType.GREEKS, subs_list)
greeks = []
async for greek in streamer.listen(EventType.GREEKS):
greeks.append(greek)
if len(greeks) >= len(subs_list):
break
print(greeks)
async with DXFeedStreamer(session) as streamer:
await streamer.subscribe(EventType.GREEKS, subs_list)
greeks = []
async for greek in streamer.listen(EventType.GREEKS):
greeks.append(greek)
if len(greeks) >= len(subs_list):
break
print(greeks)
>>> [Greeks(eventSymbol='.SPLG230616C23', eventTime=0, eventFlags=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)]

Expand All @@ -60,6 +85,7 @@ For example, we can use the streamer to create an option chain that will continu
import asyncio
from datetime import date
from dataclasses import dataclass
from tastytrade import DXFeedStreamer
from tastytrade.instruments import get_option_chain
from tastytrade.dxfeed import Greeks, Quote
Expand Down
8 changes: 4 additions & 4 deletions tastytrade/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ class TradingStatus(TastytradeJsonDataclass):
is_in_margin_call: bool
is_pattern_day_trader: bool
is_portfolio_margin_enabled: bool
is_risk_reducing_only: bool
is_small_notional_futures_intra_day_enabled: bool
is_roll_the_day_forward_enabled: bool
are_far_otm_net_options_restricted: bool
Expand All @@ -305,6 +304,7 @@ class TradingStatus(TastytradeJsonDataclass):
is_equity_offering_enabled: bool
is_equity_offering_closing_only: bool
updated_at: datetime
is_risk_reducing_only: Optional[bool] = None
day_trade_count: Optional[int] = None
autotrade_account_type: Optional[str] = None
clearing_account_number: Optional[str] = None
Expand Down Expand Up @@ -668,7 +668,7 @@ def get_history(

return [Transaction(**entry) for entry in results]

def get_transaction(self, session: Session, id: int) -> Transaction:
def get_transaction(self, session: Session, id: int) -> Transaction: # pragma: no cover
"""
Get a single transaction by ID.
Expand Down Expand Up @@ -715,7 +715,7 @@ def get_net_liquidating_value_history(
session: ProductionSession,
time_back: Optional[str] = None,
start_time: Optional[datetime] = None
) -> List[NetLiqOhlc]: # pragma: no cover
) -> List[NetLiqOhlc]:
"""
Returns a list of account net liquidating value snapshots over the
specified time period.
Expand Down Expand Up @@ -775,7 +775,7 @@ def get_effective_margin_requirements(
self,
session: ProductionSession,
symbol: str
) -> MarginRequirement: # pragma: no cover
) -> MarginRequirement:
"""
Get the effective margin requirements for a given symbol.
Expand Down
4 changes: 2 additions & 2 deletions tastytrade/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def get_options(
symbols: Optional[List[str]] = None,
active: Optional[bool] = None,
with_expired: Optional[bool] = None
) -> List['Option']:
) -> List['Option']: # pragma: no cover
"""
Returns a list of :class:`Option` objects from the given symbols.
Expand Down Expand Up @@ -1062,7 +1062,7 @@ def get_option_chain(
def get_future_option_chain(
session: ProductionSession,
symbol: str
) -> Dict[date, List[FutureOption]]: # pragma: no cover
) -> Dict[date, List[FutureOption]]:
"""
Returns a mapping of expiration date to a list of futures options
objects representing the options chain for the given symbol.
Expand Down
6 changes: 3 additions & 3 deletions tastytrade/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class MarketMetricInfo(TastytradeJsonDataclass):
def get_market_metrics(
session: ProductionSession,
symbols: List[str]
) -> List[MarketMetricInfo]: # pragma: no cover
) -> List[MarketMetricInfo]:
"""
Retrieves market metrics for the given symbols.
Expand All @@ -117,7 +117,7 @@ def get_market_metrics(
def get_dividends(
session: ProductionSession,
symbol: str
) -> List[DividendInfo]: # pragma: no cover
) -> List[DividendInfo]:
"""
Retrieves dividend information for the given symbol.
Expand All @@ -142,7 +142,7 @@ def get_earnings(
session: ProductionSession,
symbol: str,
start_date: date
) -> List[EarningsInfo]: # pragma: no cover
) -> List[EarningsInfo]:
"""
Retrieves earnings information for the given symbol.
Expand Down
2 changes: 1 addition & 1 deletion tastytrade/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(
self.validate()


class ProductionSession(Session): # pragma: no cover
class ProductionSession(Session):
"""
Contains a local user login which can then be used to interact with the
remote API.
Expand Down
Loading

0 comments on commit 6490877

Please sign in to comment.