Fix for adjusted contracts subscription bug

If quotes are pushed using the adjusted contract symbol (i.e. with
trailing '-1' suffix) the subscriber won't receive them under the
normal symbol. The logic was wrong for determining whether to add
a suffix (was failing for any symbol with an exchange suffix)
which was causing normal data feed subscriptions to fail to match
in every case.

I did some testing of the `optionsIds` parameter to the option quote
endpoint and found that it limits you to 100 symbols so it's not
practical for real-time "all-strike"" chain updating; we have to stick
to filters for now. The only real downside of this is that it seems
multiple filters across multiple symbols is quite latent. I need to
toy with it more to be sure it's not something slow on the client side.
Oh, and store option contract to ids in a `dict` for now as we may want
to try the `optionsIds` thing again down the road as I coordinate with
the QT tech team.
kivy_mainline_and_py3.8
Tyler Goodlet 2018-12-29 15:44:32 -05:00
parent dc581d0bdc
commit 1866dd1812
1 changed files with 40 additions and 26 deletions

View File

@ -107,6 +107,8 @@ class _API:
] ]
resp = await self._sess.post( resp = await self._sess.post(
path=f'/markets/quotes/options', path=f'/markets/quotes/options',
# XXX: b'{"code":1024,"message":"The size of the array requested is not valid: optionIds"}'
# ^ what I get when trying to use too many ids manually...
json={'filters': filters, 'optionIds': option_ids} json={'filters': filters, 'optionIds': option_ids}
) )
return resproc(resp, log)['optionQuotes'] return resproc(resp, log)['optionQuotes']
@ -125,8 +127,8 @@ class Client:
self.access_data = {} self.access_data = {}
self._reload_config(config) self._reload_config(config)
self._symbol_cache: Dict[str, int] = {} self._symbol_cache: Dict[str, int] = {}
self._contracts2expiries = {}
self._optids2contractinfo = {} self._optids2contractinfo = {}
self._contract2ids = {}
def _reload_config(self, config=None, **kwargs): def _reload_config(self, config=None, **kwargs):
log.warn("Reloading access config data") log.warn("Reloading access config data")
@ -317,14 +319,13 @@ class Client:
): ):
for chain in byroot['chainPerRoot']: for chain in byroot['chainPerRoot']:
optroot = chain['optionRoot'] optroot = chain['optionRoot']
suffix = ''
# handle QTs "adjusted contracts" (aka adjusted for # handle QTs "adjusted contracts" (aka adjusted for
# the underlying in some way; usually has a '(1)' in # the underlying in some way; usually has a '(1)' in
# the expiry key in their UI) # the expiry key in their UI)
adjusted_contracts = optroot != key.symbol adjusted_contracts = optroot not in key.symbol
if adjusted_contracts: tail = optroot[len(key.symbol):]
suffix = '(' + optroot[len(key.symbol):] + ')' suffix = '-' + tail if adjusted_contracts else ''
by_key[ by_key[
ContractsKey( ContractsKey(
@ -344,12 +345,16 @@ class Client:
for key, contract_type in ( for key, contract_type in (
('callSymbolId', 'call'), ('putSymbolId', 'put') ('callSymbolId', 'call'), ('putSymbolId', 'put')
): ):
self._optids2contractinfo[ contract_int_id = ids[key]
ids[key]] = { self._optids2contractinfo[contract_int_id] = {
'strike': strike, 'strike': strike,
'expiry': tup.expiry, 'expiry': tup.expiry,
'contract_type': contract_type, 'contract_type': contract_type,
'contract_key': tup,
} }
# store ids per contract
self._contract2ids.setdefault(
tup, set()).add(contract_int_id)
return by_key return by_key
async def option_chains( async def option_chains(
@ -359,22 +364,31 @@ class Client:
) -> Dict[str, Dict[str, Dict[str, Any]]]: ) -> Dict[str, Dict[str, Dict[str, Any]]]:
"""Return option chain snap quote for each ticker in ``symbols``. """Return option chain snap quote for each ticker in ``symbols``.
""" """
batch = [] quotes = await self.api.option_quotes(contracts=contracts)
for key, bystrike in contracts.items(): # XXX the below doesn't work so well due to the symbol count
quotes = await self.api.option_quotes({key: bystrike}) # limit per quote request
# quotes = await self.api.option_quotes(option_ids=list(contract_ids))
for quote in quotes: for quote in quotes:
id = quote['symbolId']
contract_info = self._optids2contractinfo[id].copy()
key = contract_info.pop('contract_key')
# XXX TODO: this currently doesn't handle adjusted contracts
# (i.e. ones that we stick a '(1)' after)
# index by .symbol, .expiry since that's what # index by .symbol, .expiry since that's what
# a subscriber (currently) sends initially # a subscriber (currently) sends initially
quote['key'] = (key[0], key[2]) quote['key'] = (key.symbol, key.expiry)
# update with expiry and strike (Obviously the # update with expiry and strike (Obviously the
# QT api designers are using some kind of severely # QT api designers are using some kind of severely
# stupid disparate table system where they keep # stupid disparate table system where they keep
# contract info in a separate table from the quote format # contract info in a separate table from the quote format
# keys. I'm really not surprised though - windows shop..) # keys. I'm really not surprised though - windows shop..)
quote.update(self._optids2contractinfo[quote['symbolId']]) # quote.update(self._optids2contractinfo[quote['symbolId']])
batch.extend(quotes) quote.update(contract_info)
return batch return quotes
async def token_refresher(client): async def token_refresher(client):