From 797efedf6a1d1a5de4e75b08904333136cdec664 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 29 Jan 2018 12:45:48 -0500 Subject: [PATCH] Add quote polling; pseudo-streaming Add a ``poll_tickers`` coro which can be used to "stream" quotes at a requested rate. Expose through a cli subcommand `piker stream`. Drop the `pikerd` command for now. --- piker/brokers/cli.py | 25 +++++++++++++++---------- piker/brokers/questrade.py | 32 +++++++++++++++++++++++++++++++- setup.py | 1 - 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/piker/brokers/cli.py b/piker/brokers/cli.py index 11678f7f..b5501633 100644 --- a/piker/brokers/cli.py +++ b/piker/brokers/cli.py @@ -1,7 +1,6 @@ """ Console interface to broker client/daemons. """ -import json from functools import partial from importlib import import_module @@ -23,15 +22,6 @@ def run(main, loglevel='info'): log.debug("Exiting piker") -@click.command() -@click.option('--broker', default='questrade', help='Broker backend to use') -@click.option('--loglevel', '-l', default='info', help='Logging level') -def pikerd(broker, loglevel): - # import broker module daemon entry point - brokermod = import_module('.' + broker, 'piker.brokers') - run(brokermod.serve_forever, loglevel) - - @click.group() def cli(): pass @@ -59,3 +49,18 @@ def api(meth, kwargs, loglevel, broker): data = run(partial(brokermod.api, meth, **_kwargs), loglevel=loglevel) if data: click.echo(colorize_json(data)) + + +@cli.command() +@click.option('--broker', default='questrade', help='Broker backend to use') +@click.option('--loglevel', '-l', default='info', help='Logging level') +@click.argument('tickers', nargs=-1) +def stream(broker, loglevel, tickers): + # import broker module daemon entry point + bm = import_module('.' + broker, 'piker.brokers') + run( + partial(bm.serve_forever, [ + partial(bm.poll_tickers, tickers=tickers) + ]), + loglevel + ) diff --git a/piker/brokers/questrade.py b/piker/brokers/questrade.py index f2661a73..3c30efea 100644 --- a/piker/brokers/questrade.py +++ b/piker/brokers/questrade.py @@ -140,6 +140,17 @@ class Client: self._prep_sess() return self.access_data + async def tickers2ids(self, tickers): + """Helper routine that take a sequence of ticker symbols and returns + their corresponding QT symbol ids. + """ + data = await self.api.symbols(names=','.join(tickers)) + symbols2ids = {} + for ticker, symbol in zip(tickers, data['symbols']): + symbols2ids[symbol['symbol']] = symbol['symbolId'] + + return symbols2ids + class API: """Questrade API at its finest. @@ -234,15 +245,34 @@ async def get_client() -> Client: write_conf(client) -async def serve_forever() -> None: +async def serve_forever(tasks) -> None: """Start up a client and serve until terminated. """ async with get_client() as client: # pretty sure this doesn't work # await client._revoke_auth_token() + async with trio.open_nursery() as nursery: + # launch token manager nursery.start_soon(token_refresher, client) + # launch children + for task in tasks: + nursery.start_soon(task, client) + + +async def poll_tickers(client, tickers, rate=2): + """Auto-poll snap quotes for a sequence of tickers at the given ``rate`` + per second. + """ + t2ids = await client.tickers2ids(tickers) + sleeptime = 1. / rate + ids = ','.join(map(str, t2ids.values())) + + while True: # use an event here to trigger exit? + quote_data = await client.api.quotes(ids=ids) + await trio.sleep(sleeptime) + async def api(methname, **kwargs) -> dict: """Make (proxy) through an api call by name and return its result. diff --git a/setup.py b/setup.py index f5edd117..59974c6e 100755 --- a/setup.py +++ b/setup.py @@ -28,7 +28,6 @@ setup( ], entry_points={ 'console_scripts': [ - 'pikerd = piker.brokers.cli:pikerd', 'piker = piker.brokers.cli:cli', ] },