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(
|
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):
|
||||||
|
|
Loading…
Reference in New Issue