Skip to content

Commit

Permalink
have market data streaming handlers be more flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
amamparo committed Nov 17, 2023
1 parent c40852a commit b39b90c
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 167 deletions.
2 changes: 1 addition & 1 deletion docs/contributors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Python 3.8+
* [pyenv](https://github.com/pyenv/pyenv#installation) recommended for managing python versions
* [Poetry](https://python-poetry.org/docs/) - for managing dependencies and virtual environments
* The [GitHub CLI tool](https://cli.github.com/) - used by the `release` scripts to open PRs
* The [hub CLI tool](https://github.com/github/hub#installation) - used by the `release` scripts to open PRs in github
* Recommended IDE: [PyCharm](https://www.jetbrains.com/pycharm/download/)

## Getting Started
Expand Down
21 changes: 3 additions & 18 deletions docs/users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,9 @@ tasty.logout()
## Streaming Market Data
```python
from tastytrade_sdk import Tastytrade
from tastytrade_sdk.market_data.models import Quote, Candle, Greeks

tasty = Tastytrade().login(login='[email protected]', password='password')


# Define some event handlers
def on_quote(quote: Quote):
print(quote)


def on_candle(candle: Candle):
print(candle)


def on_greeks(greeks: Greeks):
print(greeks)


# Subscribing to symbols across different instrument types
symbols = [
'BTC/USD',
Expand All @@ -67,9 +52,9 @@ symbols = [

subscription = tasty.market_data.subscribe(
symbols=symbols,
on_quote=on_quote,
on_candle=on_candle,
on_greeks=on_greeks
on_quote=print,
on_candle=print,
on_greeks=print
)

# start streaming
Expand Down
2 changes: 1 addition & 1 deletion release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ git add pyproject.toml
git commit -m "Bumping to next pre-patch version ${NEW_PREPATCH_VERSION}"
git push

gh pr create -t "Release ${NEW_VERSION}" -b ""
hub pull-request -m "Release ${NEW_VERSION}"
3 changes: 1 addition & 2 deletions src/tastytrade_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
# Make these classes visible in the auto-generated documentation
__all__ = [
'Tastytrade',
'MarketData', 'Subscription', 'Quote', 'Candle', 'Greeks',
'MarketData', 'Subscription',
'Api'
]

from tastytrade_sdk.api import Api, QueryParams
from tastytrade_sdk.market_data.market_data import MarketData
from tastytrade_sdk.market_data.models import Quote, Candle, Greeks
from tastytrade_sdk.market_data.subscription import Subscription
from tastytrade_sdk.tastytrade import Tastytrade
24 changes: 13 additions & 11 deletions src/tastytrade_sdk/market_data/market_data.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from typing import List, Callable, Optional
from typing import List, Callable

from injector import inject

from tastytrade_sdk.api import Api
from tastytrade_sdk.market_data.streamer_symbol_translation import StreamerSymbolTranslationsFactory
from tastytrade_sdk.market_data.subscription import Subscription
from tastytrade_sdk.market_data.models import Candle, Quote, Greeks


class MarketData:
Expand All @@ -19,22 +18,25 @@ def __init__(self, api: Api, streamer_symbol_translations_factory: StreamerSymbo
self.__api = api
self.__streamer_symbol_translations_factory = streamer_symbol_translations_factory

def subscribe(self, symbols: List[str], on_quote: Optional[Callable[[Quote], None]] = None,
on_candle: Optional[Callable[[Candle], None]] = None,
on_greeks: Optional[Callable[[Greeks], None]] = None) -> Subscription:
def subscribe(self,
symbols: List[str],
on_candle: Callable[[dict], None] = None,
on_greeks: Callable[[dict], None] = None,
on_quote: Callable[[dict], None] = None
) -> Subscription:
"""
Subscribe to live feed data
:param symbols: Symbols to subscribe to. Can be across multiple instrument types.
:param on_quote: Handler for `Quote` events
:param on_candle: Handler for `Candle` events
:param on_greeks: Handler for `Greeks` events
:param on_candle: Handler for candle events
:param on_greeks: Handler for greeks events
:param on_quote: Handler for quote events
"""
data = self.__api.get('/quote-streamer-tokens')['data']
return Subscription(
data['dxlink-url'],
data['token'],
self.__streamer_symbol_translations_factory.create(symbols),
on_quote,
on_candle,
on_greeks
on_candle=on_candle,
on_greeks=on_greeks,
on_quote=on_quote
)
85 changes: 0 additions & 85 deletions src/tastytrade_sdk/market_data/models.py

This file was deleted.

25 changes: 13 additions & 12 deletions src/tastytrade_sdk/market_data/streamer_symbol_translation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import urllib.parse
from typing import List, Optional, Tuple, Any
from typing import List, Optional, Tuple, Any, Dict

from bidict import bidict
from injector import inject
Expand All @@ -8,8 +8,8 @@


class StreamerSymbolTranslations:
def __init__(self, translations: List[Tuple[str, str]]):
self.__bidict = bidict(dict(translations))
def __init__(self, translations: Dict[str, str]):
self.__bidict = bidict(translations)

def get_streamer_symbol(self, symbol: str) -> str:
return self.__bidict.get(symbol)
Expand All @@ -28,19 +28,20 @@ def __init__(self, api: Api):
self.__api = api

def create(self, symbols: List[str]) -> StreamerSymbolTranslations:
equities = self.__get_symbol_translations('equities', symbols)
futures = self.__get_symbol_translations('futures', symbols)
equity_options = self.__get_symbol_translations('equity-options', symbols, [('with-expired', True)])
future_options = self.__get_symbol_translations('future-options', symbols)
cryptos = self.__get_symbol_translations('cryptocurrencies', symbols)
return StreamerSymbolTranslations(equities + futures + equity_options + future_options + cryptos)
return StreamerSymbolTranslations({
**self.__get_symbol_translations('equities', symbols),
**self.__get_symbol_translations('futures', symbols),
**self.__get_symbol_translations('equity-options', symbols, [('with-expired', True)]),
**self.__get_symbol_translations('future-options', symbols),
**self.__get_symbol_translations('cryptocurrencies', symbols)
})

def __get_symbol_translations(self, path_key: str, symbols: Optional[List[str]] = None,
extra_params: Optional[List[Tuple[str, Any]]] = None) -> List[Tuple[str, str]]:
extra_params: Optional[List[Tuple[str, Any]]] = None) -> Dict[str, str]:
if not symbols:
return []
return {}
items = self.__api.get(
f'/instruments/{path_key}',
params=[('symbol[]', urllib.parse.quote(x.upper())) for x in symbols or []] + (extra_params or [])
).get('data').get('items')
return [(x['symbol'], x['streamer-symbol']) for x in items if 'streamer-symbol' in x]
return {x['symbol']: x['streamer-symbol'] for x in items if 'streamer-symbol' in x}
41 changes: 8 additions & 33 deletions src/tastytrade_sdk/market_data/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from websockets.sync.client import connect, ClientConnection

from tastytrade_sdk.exceptions import TastytradeSdkException, InvalidArgument
from tastytrade_sdk.market_data.models import Candle, Quote, Greeks
from tastytrade_sdk.market_data.streamer_symbol_translation import StreamerSymbolTranslations


Expand Down Expand Up @@ -45,9 +44,10 @@ class Subscription:
__is_authorized: bool = False

def __init__(self, url: str, token: str, streamer_symbol_translations: StreamerSymbolTranslations,
on_quote: Optional[Callable[[Quote], None]] = None,
on_candle: Optional[Callable[[Candle], None]] = None,
on_greeks: Optional[Callable[[Greeks], None]] = None):
on_candle: Callable[[dict], None] = None,
on_greeks: Callable[[dict], None] = None,
on_quote: Callable[[dict], None] = None
):
"""@private"""

if not (on_quote or on_candle or on_greeks):
Expand Down Expand Up @@ -117,38 +117,13 @@ def __receive(self) -> None:
def __handle_feed_event(self, event: dict) -> None:
event_type = event['eventType']
original_symbol = self.__streamer_symbol_translations.get_original_symbol(event['eventSymbol'])
event['symbol'] = original_symbol
if event_type == 'Quote' and self.__on_quote:
self.__on_quote(Quote(
symbol=original_symbol,
bid_price=event['bidPrice'],
bid_size=event['bidSize'],
bid_exchange_code=event['bidExchangeCode'],
ask_price=event['askPrice'],
ask_size=event['askSize'],
ask_exchange_code=event['askExchangeCode']
))
self.__on_quote(event)
elif event_type == 'Candle' and self.__on_candle:
self.__on_candle(Candle(
symbol=original_symbol,
time=event['time'],
_open=event['open'],
high=event['high'],
low=event['low'],
close=event['close'],
volume=event['volume']
))
self.__on_candle(event)
elif event_type == 'Greeks' and self.__on_greeks:
self.__on_greeks(Greeks(
symbol=original_symbol,
time=event['time'],
price=event['price'],
volatility=event['volatility'],
delta=event['delta'],
gamma=event['gamma'],
theta=event['theta'],
vega=event['vega'],
rho=event['rho']
))
self.__on_greeks(event)
else:
logging.debug('Unhandled feed event type %s for symbol %s', event_type, original_symbol)

Expand Down
4 changes: 2 additions & 2 deletions src/tastytrade_sdk/tastytrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def __init__(self, api_base_url: str = 'api.tastytrade.com'):
(when using the sandbox environment, for e.g.)
"""

def configure(binder):
def __configure(binder):
binder.bind(Config, to=Config(api_base_url=api_base_url))

self.__container = Injector(configure)
self.__container = Injector(__configure)

def login(self, login: str, password: str) -> 'Tastytrade':
"""
Expand Down
6 changes: 4 additions & 2 deletions tests/market_data_experiment.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import time

from tests.utils import get_tasty


def main():
tasty = get_tasty()
symbols = ['BTC/USD', 'SPY', 'ETH/USD', '/ESU3', 'SPY 230630C00255000', './ESU3 EW2N3 230714C4310', 'foo', 'bar']
subscription = tasty.market_data.subscribe(symbols=symbols, on_quote=print, on_candle=print, on_greeks=print)
symbols = ['YELP 240517C00042000', 'AAPL']
subscription = tasty.market_data.subscribe(symbols=symbols, on_quote=print, on_candle=print, on_greeks=print, on_trade=print)
subscription.open()


Expand Down

0 comments on commit b39b90c

Please sign in to comment.