Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

streamers use async context managers; prod session in testing #104

Merged
merged 4 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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. Go to the "Actions" page on the forked repository and enable actions.
4. Navigate to the forked repository's settings page and click on "Secrets and variables" > "Actions".
5. Click on "New repository secret" to add your Tastytrade username named `TT_USERNAME`.
6. Finally, do the same with your password, naming it `TT_PASSWORD`.
4 changes: 3 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
Fixes ...

## Pre-merge checklist
- [ ] Passing tests
- [ ] Passing tests LOCALLY
- [ ] 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.
13 changes: 4 additions & 9 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,18 @@ name: Python application
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

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 +28,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
12 changes: 8 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,11 @@ 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 +719,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 +779,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