Skip to content

Commit

Permalink
big docs update; prepare v6.3; add watchlist tests (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
Graeme22 authored Nov 28, 2023
1 parent 77a0fc5 commit cc966b3
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 146 deletions.
42 changes: 11 additions & 31 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,11 @@ The streamer is a websocket connection to dxfeed (the Tastytrade data provider)
from tastytrade.dxfeed import EventType
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)
subs_list = ['SPY'] # list of symbols to subscribe to
await streamer.subscribe(EventType.QUOTE, subs_list)
# this example fetches quotes once, then exits
quote await streamer.get_event(EventType.QUOTE)
print(quote)
>>> [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 @@ -69,18 +65,6 @@ Getting current positions
>>> CurrentPosition(account_number='5WV69754', 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='Credit', is_suppressed=False, is_frozen=False, realized_day_gain=Decimal('7.888'), realized_day_gain_effect='Credit', realized_day_gain_date=datetime.date(2023, 5, 19), realized_today=Decimal('0.512'), realized_today_effect='Debit', 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)

Symbol search
-------------

.. code-block:: python
from tastytrade import symbol_search
results = symbol_search(session, 'AAP')
print(results)
>>> [SymbolData(symbol='AAP', description='Advance Auto Parts Inc.'), SymbolData(symbol='AAPD', description='Direxion Daily AAPL Bear 1X Shares'), SymbolData(symbol='AAPL', description='Apple Inc. - Common Stock'), SymbolData(symbol='AAPB', description='GraniteShares 1.75x Long AAPL Daily ETF'), SymbolData(symbol='AAPU', description='Direxion Daily AAPL Bull 1.5X Shares')]

Placing an order
----------------

Expand All @@ -96,11 +80,11 @@ Placing an order
leg = symbol.build_leg(Decimal('5'), OrderAction.BUY_TO_OPEN) # buy to open 5 shares
order = NewOrder(
time_in_force=OrderTimeInForce.DAY,
order_type=OrderType.LIMIT,
legs=[leg], # you can have multiple legs in an order
price=Decimal('50'), # limit price, here $50 for 5 shares = $10/share
price_effect=PriceEffect.DEBIT
time_in_force=OrderTimeInForce.DAY,
order_type=OrderType.LIMIT,
legs=[leg], # you can have multiple legs in an order
price=Decimal('50'), # limit price, here $50 for 5 shares = $10/share
price_effect=PriceEffect.DEBIT
)
response = account.place_order(session, order, dry_run=True) # a test order
print(response)
Expand All @@ -119,11 +103,7 @@ Options chain/streaming greeks
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
greeks = await streamer.get_event(EventType.GREEKS)
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 Down
24 changes: 23 additions & 1 deletion docs/account-streamer.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
Account Streamer
================
================

The account streamer is used to track account-level updates, such as order fills, watchlist updates and quote alerts.
Typically, you'll want a separate task running for the account streamer, which can then notify your application about important events.

Here's an example of setting up an account streamer to continuously wait for events and print them:

.. code-block:: python
from tastytrade import Account, AccountStreamer
async with AccountStreamer(session) as streamer:
accounts = Account.get_accounts(session)
# updates to balances, orders, and positions
await streamer.subscribe_accounts(accounts)
# changes in public watchlists
await streamer.subscribe_public_watchlists()
# quote alerts configured by the user
await streamer.subscribe_quote_alerts()
async for data in streamer.listen():
print(data)
44 changes: 43 additions & 1 deletion docs/accounts.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
Accounts
========
========

An account object contains information about a specific Tastytrade account. It can be used to place trades, monitor profit/loss, and analyze positions.

The easiest way to get an account is to grab all accounts associated with a specific session:

.. code-block:: python
from tastytrade import Account
accounts = Account.get_accounts(session)
You can also get a specific account by its unique ID:

.. code-block:: python
account = Account.get_account(session, '5WX01234')
The ``get_balances`` function can be used to obtain information about the current buying power and cash balance:

.. code-block:: python
balance = account.get_balances(session)
print(balance)
>>> 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)
To obtain information about current positions:

.. code-block:: python
positions = account.get_positions(session)
print(positions[0])
>>> 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))
TODO:
get_history
get_net_liquidating_value_history
get_live_orders
delete_order(and place and replace)
get_order_history

There are many more things you can do with an ``Account`` object--check out the SDK Reference section!
3 changes: 1 addition & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
project = 'tastytrade'
copyright = '2023, Graeme Holliday'
author = 'Graeme Holliday'
release = '6.2'
release = '6.3'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down Expand Up @@ -50,4 +50,3 @@
autodoc_pydantic_model_undoc_members = False
autodoc_pydantic_model_hide_paramlist = False
autodoc_pydantic_model_show_config_summary = False

110 changes: 53 additions & 57 deletions docs/data-streamer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Or, you can create a streamer using an asynchronous context manager:
from tastytrade import DXLinkStreamer
async with DXLinkStreamer(session) as streamer:
pass
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``:
Expand All @@ -29,13 +29,13 @@ Once you've created the streamer, you can subscribe/unsubscribe to events, like
subs_list = ['SPY', 'SPX']
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)
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')]

Expand All @@ -44,10 +44,10 @@ Note that these are ``asyncio`` calls, so you'll need to run this code asynchron
.. 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)
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())
Expand All @@ -66,13 +66,9 @@ We can also use the streamer to stream greeks for options symbols:
subs_list = [chain[date(2023, 6, 16)][0].streamer_symbol]
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)
await streamer.subscribe(EventType.GREEKS, subs_list)
greeks = await streamer.get_event(EventType.GREEKS)
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 @@ -93,50 +89,50 @@ For example, we can use the streamer to create an option chain that will continu
@dataclass
class LivePrices:
quotes: dict[str, Quote]
greeks: dict[str, Greeks]
streamer: DXFeedStreamer
puts: list[Option]
calls: list[Option]
@classmethod
async def create(
cls,
session: ProductionSession,
symbol: str = 'SPY',
expiration: date = date.today()
):
chain = get_option_chain(session, symbol)
options = [o for o in chain[expiration]]
# the `streamer_symbol` property is the symbol used by the streamer
streamer_symbols = [o.streamer_symbol for o in options]
streamer = await DXFeedStreamer.create(session)
# subscribe to quotes and greeks for all options on that date
await streamer.subscribe(EventType.QUOTE, [symbol] + streamer_symbols)
await streamer.subscribe(EventType.GREEKS, streamer_symbols)
quotes: dict[str, Quote]
greeks: dict[str, Greeks]
streamer: DXFeedStreamer
puts: list[Option]
calls: list[Option]
@classmethod
async def create(
cls,
session: ProductionSession,
symbol: str = 'SPY',
expiration: date = date.today()
):
chain = get_option_chain(session, symbol)
options = [o for o in chain[expiration]]
# the `streamer_symbol` property is the symbol used by the streamer
streamer_symbols = [o.streamer_symbol for o in options]
streamer = await DXFeedStreamer.create(session)
# subscribe to quotes and greeks for all options on that date
await streamer.subscribe(EventType.QUOTE, [symbol] + streamer_symbols)
await streamer.subscribe(EventType.GREEKS, streamer_symbols)
puts = [o for o in options if o.option_type == OptionType.PUT]
calls = [o for o in options if o.option_type == OptionType.CALL]
self = cls({}, {}, streamer, puts, calls)
puts = [o for o in options if o.option_type == OptionType.PUT]
calls = [o for o in options if o.option_type == OptionType.CALL]
self = cls({}, {}, streamer, puts, calls)
t_listen_greeks = asyncio.create_task(self._update_greeks())
t_listen_quotes = asyncio.create_task(self._update_quotes())
asyncio.gather(t_listen_greeks, t_listen_quotes)
t_listen_greeks = asyncio.create_task(self._update_greeks())
t_listen_quotes = asyncio.create_task(self._update_quotes())
asyncio.gather(t_listen_greeks, t_listen_quotes)
# wait we have quotes and greeks for each option
while len(self.greeks) != len(options) or len(self.quotes) != len(options):
await asyncio.sleep(0.1)
# wait we have quotes and greeks for each option
while len(self.greeks) != len(options) or len(self.quotes) != len(options):
await asyncio.sleep(0.1)
return self
return self
async def _update_greeks(self):
async for e in self.streamer.listen(EventType.GREEKS):
self.greeks[e.eventSymbol] = e
async def _update_greeks(self):
async for e in self.streamer.listen(EventType.GREEKS):
self.greeks[e.eventSymbol] = e
async def _update_quotes(self):
async for e in self.streamer.listen(EventType.QUOTE):
self.quotes[e.eventSymbol] = e
async def _update_quotes(self):
async for e in self.streamer.listen(EventType.QUOTE):
self.quotes[e.eventSymbol] = e
Now, we can access the quotes and greeks at any time, and they'll be up-to-date with the live prices from the streamer:

Expand Down
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

installation
sessions
accounts
instruments
orders
account-streamer
data-streamer
watchlists

Expand Down
14 changes: 13 additions & 1 deletion docs/instruments.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
Instruments
===========
===========

TODO:
Cryptocurrency
Equity
Option
NestedOptionChain
get_option_chain
Future
FutureOption
NestedFutureOptionChain
Warrant
get_future_option_chain
Loading

0 comments on commit cc966b3

Please sign in to comment.