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
parent
dc581d0bdc
commit
1866dd1812
|
@ -107,6 +107,8 @@ class _API:
|
|||
]
|
||||
resp = await self._sess.post(
|
||||
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}
|
||||
)
|
||||
return resproc(resp, log)['optionQuotes']
|
||||
|
@ -125,8 +127,8 @@ class Client:
|
|||
self.access_data = {}
|
||||
self._reload_config(config)
|
||||
self._symbol_cache: Dict[str, int] = {}
|
||||
self._contracts2expiries = {}
|
||||
self._optids2contractinfo = {}
|
||||
self._contract2ids = {}
|
||||
|
||||
def _reload_config(self, config=None, **kwargs):
|
||||
log.warn("Reloading access config data")
|
||||
|
@ -317,14 +319,13 @@ class Client:
|
|||
):
|
||||
for chain in byroot['chainPerRoot']:
|
||||
optroot = chain['optionRoot']
|
||||
suffix = ''
|
||||
|
||||
# handle QTs "adjusted contracts" (aka adjusted for
|
||||
# the underlying in some way; usually has a '(1)' in
|
||||
# the expiry key in their UI)
|
||||
adjusted_contracts = optroot != key.symbol
|
||||
if adjusted_contracts:
|
||||
suffix = '(' + optroot[len(key.symbol):] + ')'
|
||||
adjusted_contracts = optroot not in key.symbol
|
||||
tail = optroot[len(key.symbol):]
|
||||
suffix = '-' + tail if adjusted_contracts else ''
|
||||
|
||||
by_key[
|
||||
ContractsKey(
|
||||
|
@ -344,12 +345,16 @@ class Client:
|
|||
for key, contract_type in (
|
||||
('callSymbolId', 'call'), ('putSymbolId', 'put')
|
||||
):
|
||||
self._optids2contractinfo[
|
||||
ids[key]] = {
|
||||
'strike': strike,
|
||||
'expiry': tup.expiry,
|
||||
'contract_type': contract_type,
|
||||
}
|
||||
contract_int_id = ids[key]
|
||||
self._optids2contractinfo[contract_int_id] = {
|
||||
'strike': strike,
|
||||
'expiry': tup.expiry,
|
||||
'contract_type': contract_type,
|
||||
'contract_key': tup,
|
||||
}
|
||||
# store ids per contract
|
||||
self._contract2ids.setdefault(
|
||||
tup, set()).add(contract_int_id)
|
||||
return by_key
|
||||
|
||||
async def option_chains(
|
||||
|
@ -359,22 +364,31 @@ class Client:
|
|||
) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
||||
"""Return option chain snap quote for each ticker in ``symbols``.
|
||||
"""
|
||||
batch = []
|
||||
for key, bystrike in contracts.items():
|
||||
quotes = await self.api.option_quotes({key: bystrike})
|
||||
for quote in quotes:
|
||||
# index by .symbol, .expiry since that's what
|
||||
# a subscriber (currently) sends initially
|
||||
quote['key'] = (key[0], key[2])
|
||||
# update with expiry and strike (Obviously the
|
||||
# QT api designers are using some kind of severely
|
||||
# stupid disparate table system where they keep
|
||||
# contract info in a separate table from the quote format
|
||||
# keys. I'm really not surprised though - windows shop..)
|
||||
quote.update(self._optids2contractinfo[quote['symbolId']])
|
||||
batch.extend(quotes)
|
||||
quotes = await self.api.option_quotes(contracts=contracts)
|
||||
# XXX the below doesn't work so well due to the symbol count
|
||||
# limit per quote request
|
||||
# quotes = await self.api.option_quotes(option_ids=list(contract_ids))
|
||||
for quote in quotes:
|
||||
id = quote['symbolId']
|
||||
contract_info = self._optids2contractinfo[id].copy()
|
||||
key = contract_info.pop('contract_key')
|
||||
|
||||
return batch
|
||||
# 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
|
||||
# a subscriber (currently) sends initially
|
||||
quote['key'] = (key.symbol, key.expiry)
|
||||
|
||||
# update with expiry and strike (Obviously the
|
||||
# QT api designers are using some kind of severely
|
||||
# stupid disparate table system where they keep
|
||||
# contract info in a separate table from the quote format
|
||||
# keys. I'm really not surprised though - windows shop..)
|
||||
# quote.update(self._optids2contractinfo[quote['symbolId']])
|
||||
quote.update(contract_info)
|
||||
|
||||
return quotes
|
||||
|
||||
|
||||
async def token_refresher(client):
|
||||
|
|
Loading…
Reference in New Issue