Add `Client.search_symbols()` to all backends, use it in `piker search`

symbol_search
Tyler Goodlet 2021-05-28 12:29:58 -04:00
parent c56c7b8540
commit 7fa9f3f542
7 changed files with 105 additions and 14 deletions

View File

@ -142,8 +142,14 @@ async def maybe_open_runtime(
Start the ``tractor`` runtime (a root actor) if none exists. Start the ``tractor`` runtime (a root actor) if none exists.
""" """
settings = _tractor_kwargs
settings.update(kwargs)
if not tractor.current_actor(err_on_no_runtime=False): if not tractor.current_actor(err_on_no_runtime=False):
async with tractor.open_root_actor(loglevel=loglevel, **kwargs): async with tractor.open_root_actor(
loglevel=loglevel,
**settings,
):
yield yield
else: else:
yield yield

View File

@ -207,6 +207,25 @@ class Client:
return self._pairs return self._pairs
async def search_symbols(
self,
pattern: str,
limit: int = None,
) -> Dict[str, Any]:
if self._pairs is not None:
data = self._pairs
else:
data = await self.symbol_info()
matches = fuzzy.extractBests(
pattern,
data,
score_cutoff=50,
)
# repack in dict form
return {item[0]['symbol']: item[0]
for item in matches}
async def bars( async def bars(
self, self,
symbol: str, symbol: str,

View File

@ -30,7 +30,7 @@ import tractor
from ..cli import cli from ..cli import cli
from .. import watchlists as wl from .. import watchlists as wl
from ..log import get_console_log, colorize_json, get_logger from ..log import get_console_log, colorize_json, get_logger
from .._daemon import maybe_spawn_brokerd from .._daemon import maybe_spawn_brokerd, maybe_open_pikerd
from ..brokers import core, get_brokermod, data from ..brokers import core, get_brokermod, data
log = get_logger('cli') log = get_logger('cli')
@ -273,13 +273,25 @@ def search(config, pattern):
"""Search for symbols from broker backend(s). """Search for symbols from broker backend(s).
""" """
# global opts # global opts
brokermod = config['brokermods'][0] brokermods = config['brokermods']
quotes = tractor.run( # define tractor entrypoint
partial(core.symbol_search, brokermod, pattern), async def main(func):
start_method='forkserver',
loglevel='info', async with maybe_open_pikerd(
loglevel=config['loglevel'],
):
return await func()
quotes = trio.run(
main,
partial(
core.symbol_search,
brokermods,
pattern,
),
) )
if not quotes: if not quotes:
log.error(f"No matches could be found for {pattern}?") log.error(f"No matches could be found for {pattern}?")
return return

View File

@ -24,8 +24,12 @@ import inspect
from types import ModuleType from types import ModuleType
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
import trio
from ..log import get_logger from ..log import get_logger
from . import get_brokermod from . import get_brokermod
from .._daemon import maybe_spawn_brokerd
from .api import open_cached_client
log = get_logger(__name__) log = get_logger(__name__)
@ -126,13 +130,41 @@ async def symbol_info(
return await client.symbol_info(symbol, **kwargs) return await client.symbol_info(symbol, **kwargs)
async def search_w_brokerd(name: str, pattern: str) -> dict:
async with open_cached_client(name) as client:
# TODO: support multiple asset type concurrent searches.
return await client.search_symbols(pattern=pattern)
async def symbol_search( async def symbol_search(
brokermod: ModuleType, brokermods: list[ModuleType],
pattern: str, pattern: str,
**kwargs, **kwargs,
) -> Dict[str, Dict[str, Dict[str, Any]]]: ) -> Dict[str, Dict[str, Dict[str, Any]]]:
"""Return symbol info from broker. """Return symbol info from broker.
""" """
async with brokermod.get_client() as client: results = []
# TODO: support multiple asset type concurrent searches.
return await client.search_stocks(pattern=pattern, **kwargs) async def search_backend(brokername: str) -> None:
async with maybe_spawn_brokerd(
brokername,
) as portal:
results.append((
brokername,
await portal.run(
search_w_brokerd,
name=brokername,
pattern=pattern,
),
))
async with trio.open_nursery() as n:
for mod in brokermods:
n.start_soon(search_backend, mod.name)
return results

View File

@ -52,7 +52,7 @@ from ..log import get_logger, get_console_log
from .._daemon import maybe_spawn_brokerd from .._daemon import maybe_spawn_brokerd
from ..data._source import from_df from ..data._source import from_df
from ..data._sharedmem import ShmArray from ..data._sharedmem import ShmArray
from ._util import SymbolNotFound from ._util import SymbolNotFound, NoData
log = get_logger(__name__) log = get_logger(__name__)
@ -311,6 +311,18 @@ class Client:
else: else:
return {} return {}
async def search_symbols(
self,
pattern: str,
# how many contracts to search "up to"
upto: int = 3,
asdicts: bool = True,
) -> Dict[str, ContractDetails]:
# TODO add search though our adhoc-locally defined symbol set
# for futes/cmdtys/
return await self.search_stocks(pattern, upto, asdicts)
async def search_futes( async def search_futes(
self, self,
pattern: str, pattern: str,
@ -862,6 +874,13 @@ async def get_bars(
# throttling despite the rps being low # throttling despite the rps being low
break break
elif 'No market data permissions for' in err.message:
# TODO: signalling for no permissions searches
raise NoData(f'Symbol: {sym}')
break
else: else:
log.exception( log.exception(
"Data query rate reached: Press `ctrl-alt-f`" "Data query rate reached: Press `ctrl-alt-f`"
@ -1133,8 +1152,10 @@ async def stream_quotes(
# tell caller quotes are now coming in live # tell caller quotes are now coming in live
feed_is_live.set() feed_is_live.set()
# last = time.time()
async with aclosing(stream): async with aclosing(stream):
async for ticker in stream: async for ticker in stream:
# print(f'ticker rate: {1/(time.time() - last)}')
# print(ticker.vwap) # print(ticker.vwap)
quote = normalize( quote = normalize(
@ -1149,6 +1170,7 @@ async def stream_quotes(
# ugh, clear ticks since we've consumed them # ugh, clear ticks since we've consumed them
ticker.ticks = [] ticker.ticks = []
# last = time.time()
def pack_position(pos: Position) -> Dict[str, Any]: def pack_position(pos: Position) -> Dict[str, Any]:

View File

@ -207,7 +207,7 @@ class Client:
return self._pairs return self._pairs
async def search_stocks( async def search_symbols(
self, self,
pattern: str, pattern: str,
limit: int = None, limit: int = None,

View File

@ -628,7 +628,7 @@ class Client:
f"Took {time.time() - start} seconds to retreive {len(bars)} bars") f"Took {time.time() - start} seconds to retreive {len(bars)} bars")
return bars return bars
async def search_stocks( async def search_symbols(
self, self,
pattern: str, pattern: str,
# how many contracts to return # how many contracts to return