Drop need for `ib_insync.IB.qualifyContractsAsync()' mod

As per https://github.com/erdewit/ib_insync/pull/454 the more correct
way to do this is with `.reqContractDetailsAsync()` which we wrap with
`Client.con_deats()` and which works just as well. Further drop all the
`dict`-ifying that was being done in that method and instead always
return `ContractDetails` object in an fqsn-like explicitly keyed `dict`.
broker_bumpz
Tyler Goodlet 2022-04-11 14:54:16 -04:00
parent 8b1c521ae9
commit 4d23f6e4d7
1 changed files with 35 additions and 47 deletions

View File

@ -355,28 +355,34 @@ class Client:
# batch request all details # batch request all details
results = await asyncio.gather(*futs) results = await asyncio.gather(*futs)
# XXX: if there is more then one entry in the details list # one set per future result
details = {} details = {}
for details_set in results: for details_set in results:
# XXX: if there is more then one entry in the details list
# then the contract is so called "ambiguous". # then the contract is so called "ambiguous".
for d in details_set: for d in details_set:
con = d.contract con = d.contract
unique_sym = f'{con.symbol}.{con.primaryExchange}'
as_dict = asdict(d) key = '.'.join([
con.symbol,
con.primaryExchange or con.exchange,
])
expiry = con.lastTradeDateOrContractMonth
if expiry:
key += f'.{expiry}'
# nested dataclass we probably don't need and that # nested dataclass we probably don't need and that
# won't IPC serialize # won't IPC serialize..
as_dict.pop('secIdList') d.secIdList = ''
details[unique_sym] = as_dict details[key] = d
return details return details
async def search_stocks( async def search_stocks(
self, self,
pattern: str, pattern: str,
get_details: bool = False,
upto: int = 3, # how many contracts to search "up to" upto: int = 3, # how many contracts to search "up to"
) -> dict[str, ContractDetails]: ) -> dict[str, ContractDetails]:
@ -388,31 +394,13 @@ class Client:
''' '''
descriptions = await self.ib.reqMatchingSymbolsAsync(pattern) descriptions = await self.ib.reqMatchingSymbolsAsync(pattern)
if descriptions is not None: if descriptions is None:
descrs = descriptions[:upto]
if get_details:
deats = await self.con_deats([d.contract for d in descrs])
return deats
else:
results = {}
for d in descrs:
con = d.contract
# sometimes there's a weird extra suffix returned
# from search?
exch = con.primaryExchange.rsplit('.')[0]
unique_sym = f'{con.symbol}.{exch}'
expiry = con.lastTradeDateOrContractMonth
if expiry:
unique_sym += f'{expiry}'
results[unique_sym] = {}
return results
else:
return {} return {}
# limit
descrs = descriptions[:upto]
return await self.con_deats([d.contract for d in descrs])
async def search_symbols( async def search_symbols(
self, self,
pattern: str, pattern: str,
@ -427,36 +415,30 @@ class Client:
results = await self.search_stocks( results = await self.search_stocks(
pattern, pattern,
upto=upto, upto=upto,
get_details=True,
) )
for key, contracts in results.copy().items(): for key, deats in results.copy().items():
tract = contracts['contract']
sym = tract['symbol'] tract = deats.contract
sym = tract.symbol
sectype = tract.secType
sectype = tract['secType']
if sectype == 'IND': if sectype == 'IND':
results[f'{sym}.IND'] = tract results[f'{sym}.IND'] = tract
results.pop(key) results.pop(key)
exch = tract['exchange'] exch = tract.exchange
if exch in _futes_venues: if exch in _futes_venues:
# try get all possible contracts for symbol as per, # try get all possible contracts for symbol as per,
# https://interactivebrokers.github.io/tws-api/basic_contracts.html#fut # https://interactivebrokers.github.io/tws-api/basic_contracts.html#fut
con = Contract( con = ibis.Future(
'FUT+CONTFUT',
symbol=sym, symbol=sym,
exchange=exch, exchange=exch,
) )
try: try:
possibles = await self.ib.qualifyContractsAsync(con) all_deats = await self.con_deats([con])
for i, condict in enumerate(sorted( results |= all_deats
map(asdict, possibles),
# sort by expiry
key=lambda con: con['lastTradeDateOrContractMonth'],
)):
expiry = condict['lastTradeDateOrContractMonth']
results[f'{sym}.{exch}.{expiry}'] = condict
except RequestError as err: except RequestError as err:
log.warning(err.message) log.warning(err.message)
@ -600,6 +582,12 @@ class Client:
raise ValueError(f"No contract could be found {con}") raise ValueError(f"No contract could be found {con}")
self._contracts[pattern] = contract self._contracts[pattern] = contract
# add an aditional entry with expiry suffix if available
conexp = contract.lastTradeDateOrContractMonth
if conexp:
self._contracts[pattern + f'.{conexp}'] = contract
return contract return contract
async def get_head_time( async def get_head_time(
@ -1640,7 +1628,7 @@ async def backfill_bars(
out, fails = await get_bars(proxy, fqsn, end_dt=first_dt) out, fails = await get_bars(proxy, fqsn, end_dt=first_dt)
if out == None: if out is None:
# could be trying to retreive bars over weekend # could be trying to retreive bars over weekend
# TODO: add logic here to handle tradable hours and # TODO: add logic here to handle tradable hours and
# only grab valid bars in the range # only grab valid bars in the range