Add `piker bars` command

For easy testing of questrade historical data from cli.
Re-org the common cli components into a new package to avoid having all
commands defined in a top-level module.
questrade_candles
Tyler Goodlet 2020-05-23 15:39:17 -04:00
parent c11946988e
commit ff843372a1
4 changed files with 137 additions and 57 deletions

View File

@ -1,9 +1,9 @@
""" """
Console interface to broker client/daemons. Console interface to broker client/daemons.
""" """
import os
from functools import partial from functools import partial
import json import json
import os
from operator import attrgetter from operator import attrgetter
from operator import itemgetter from operator import itemgetter
@ -12,62 +12,17 @@ import pandas as pd
import trio import trio
import tractor import tractor
from . import watchlists as wl from ..cli import cli
from .log import get_console_log, colorize_json, get_logger from .. import watchlists as wl
from .brokers import core, get_brokermod, data, config from ..log import get_console_log, colorize_json, get_logger
from .brokers.core import maybe_spawn_brokerd_as_subactor, _data_mods from ..brokers.core import maybe_spawn_brokerd_as_subactor
from ..brokers import core, get_brokermod, data
log = get_logger('cli') log = get_logger('cli')
DEFAULT_BROKER = 'questrade' DEFAULT_BROKER = 'questrade'
_config_dir = click.get_app_dir('piker') _config_dir = click.get_app_dir('piker')
_watchlists_data_path = os.path.join(_config_dir, 'watchlists.json') _watchlists_data_path = os.path.join(_config_dir, 'watchlists.json')
_context_defaults = dict(
default_map={
'monitor': {
'rate': 3,
},
'optschain': {
'rate': 1,
},
}
)
@click.command()
@click.option('--loglevel', '-l', default='warning', help='Logging level')
@click.option('--tl', is_flag=True, help='Enable tractor logging')
@click.option('--host', '-h', default='127.0.0.1', help='Host address to bind')
def pikerd(loglevel, host, tl):
"""Spawn the piker broker-daemon.
"""
get_console_log(loglevel)
tractor.run_daemon(
rpc_module_paths=_data_mods,
name='brokerd',
loglevel=loglevel if tl else None,
)
@click.group(context_settings=_context_defaults)
@click.option('--broker', '-b', default=DEFAULT_BROKER,
help='Broker backend to use')
@click.option('--loglevel', '-l', default='warning', help='Logging level')
@click.option('--configdir', '-c', help='Configuration directory')
@click.pass_context
def cli(ctx, broker, loglevel, configdir):
if configdir is not None:
assert os.path.isdir(configdir), f"`{configdir}` is not a valid path"
config._override_config_dir(configdir)
# ensure that ctx.obj exists even though we aren't using it (yet)
ctx.ensure_object(dict)
ctx.obj.update({
'broker': broker,
'brokermod': get_brokermod(broker),
'loglevel': loglevel,
'log': get_console_log(loglevel),
})
@cli.command() @cli.command()
@ -132,11 +87,10 @@ def quote(config, tickers, df_output):
brokermod.log.warn(f"Could not find symbol {ticker}?") brokermod.log.warn(f"Could not find symbol {ticker}?")
if df_output: if df_output:
cols = next(filter(bool, quotes.values())).copy() cols = next(filter(bool, quotes)).copy()
cols.pop('symbol') cols.pop('symbol')
df = pd.DataFrame( df = pd.DataFrame(
(quote or {} for quote in quotes.values()), (quote or {} for quote in quotes),
index=quotes.keys(),
columns=cols, columns=cols,
) )
click.echo(df) click.echo(df)
@ -144,6 +98,41 @@ def quote(config, tickers, df_output):
click.echo(colorize_json(quotes)) click.echo(colorize_json(quotes))
@cli.command()
@click.option('--df-output', '-df', flag_value=True,
help='Output in `pandas.DataFrame` format')
@click.option('--count', '-c', default=10,
help='Number of bars to retrieve')
@click.argument('symbol', required=True)
@click.pass_obj
def bars(config, symbol, count, df_output):
"""Retreive 1m bars for symbol and print on the console in json
format.
"""
# global opts
brokermod = config['brokermod']
# broker backend should return at the least a
# list of candle dictionaries
bars = trio.run(
partial(
core.bars,
brokermod,
symbol,
count=count,
)
)
if not bars:
log.error(f"No quotes could be found for {symbol}?")
return
if df_output:
click.echo(pd.DataFrame(bars))
else:
click.echo(colorize_json(bars))
@cli.command() @cli.command()
@click.option('--tl', is_flag=True, help='Enable tractor logging') @click.option('--tl', is_flag=True, help='Enable tractor logging')
@click.option('--rate', '-r', default=3, help='Quote rate limit') @click.option('--rate', '-r', default=3, help='Quote rate limit')
@ -184,6 +173,7 @@ def monitor(config, rate, name, dhost, test, tl):
name='monitor', name='monitor',
loglevel=loglevel if tl else None, loglevel=loglevel if tl else None,
rpc_module_paths=['piker.ui.monitor'], rpc_module_paths=['piker.ui.monitor'],
start_method='forkserver',
) )
@ -406,4 +396,5 @@ def optschain(config, symbol, date, tl, rate, test):
partial(main, tries=1), partial(main, tries=1),
name='kivy-options-chain', name='kivy-options-chain',
loglevel=loglevel if tl else None, loglevel=loglevel if tl else None,
start_method='forkserver',
) )

View File

@ -110,3 +110,15 @@ async def contracts(
async with brokermod.get_client() as client: async with brokermod.get_client() as client:
# return await client.get_all_contracts([symbol]) # return await client.get_all_contracts([symbol])
return await client.get_all_contracts([symbol]) return await client.get_all_contracts([symbol])
async def bars(
brokermod: ModuleType,
symbol: str,
**kwargs,
) -> Dict[str, Dict[str, Dict[str, Any]]]:
"""Return option contracts (all expiries) for ``symbol``.
"""
async with brokermod.get_client() as client:
# return await client.get_all_contracts([symbol])
return await client.bars(symbol, **kwargs)

View File

@ -570,14 +570,24 @@ class Client:
raise SymbolNotFound(symbol) raise SymbolNotFound(symbol)
sid = sids[symbol] sid = sids[symbol]
est_now = arrow.utcnow().to('US/Eastern').floor('minute')
est_start = est_now.shift(minutes=-count) # get last market open end time
est_end = now = arrow.utcnow().to('US/Eastern').floor('minute')
wd = now.isoweekday()
if wd > 5:
quotes = await self.quote([symbol])
est_end = arrow.get(quotes[0]['lastTradeTime'])
if est_end.hour == 0:
# XXX don't bother figuring out extended hours for now
est_end = est_end.replace(hour=17)
est_start = est_end.shift(minutes=-count)
start = time.time() start = time.time()
bars = await self.api.candles( bars = await self.api.candles(
sid, sid,
start=est_start.isoformat(), start=est_start.isoformat(),
end=est_now.isoformat(), end=est_end.isoformat(),
interval=_time_frames[time_frame], interval=_time_frames[time_frame],
) )
log.debug( log.debug(

View File

@ -0,0 +1,67 @@
"""
CLI commons.
"""
import os
import click
import tractor
from ..log import get_console_log, get_logger
from ..brokers import get_brokermod, config
from ..brokers.core import _data_mods
log = get_logger('cli')
DEFAULT_BROKER = 'questrade'
# _config_dir = click.get_app_dir('piker')
# _watchlists_data_path = os.path.join(_config_dir, 'watchlists.json')
_context_defaults = dict(
default_map={
'monitor': {
'rate': 3,
},
'optschain': {
'rate': 1,
},
}
)
@click.command()
@click.option('--loglevel', '-l', default='warning', help='Logging level')
@click.option('--tl', is_flag=True, help='Enable tractor logging')
@click.option('--host', '-h', default='127.0.0.1', help='Host address to bind')
def pikerd(loglevel, host, tl):
"""Spawn the piker broker-daemon.
"""
get_console_log(loglevel)
tractor.run_daemon(
rpc_module_paths=_data_mods,
name='brokerd',
loglevel=loglevel if tl else None,
)
@click.group(context_settings=_context_defaults)
@click.option('--broker', '-b', default=DEFAULT_BROKER,
help='Broker backend to use')
@click.option('--loglevel', '-l', default='warning', help='Logging level')
@click.option('--configdir', '-c', help='Configuration directory')
@click.pass_context
def cli(ctx, broker, loglevel, configdir):
if configdir is not None:
assert os.path.isdir(configdir), f"`{configdir}` is not a valid path"
config._override_config_dir(configdir)
# ensure that ctx.obj exists even though we aren't using it (yet)
ctx.ensure_object(dict)
ctx.obj.update({
'broker': broker,
'brokermod': get_brokermod(broker),
'loglevel': loglevel,
'log': get_console_log(loglevel),
})
# load downstream cli modules
from ..brokers import cli as _