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
parent
c11946988e
commit
ff843372a1
|
@ -1,9 +1,9 @@
|
|||
"""
|
||||
Console interface to broker client/daemons.
|
||||
"""
|
||||
import os
|
||||
from functools import partial
|
||||
import json
|
||||
import os
|
||||
from operator import attrgetter
|
||||
from operator import itemgetter
|
||||
|
||||
|
@ -12,62 +12,17 @@ import pandas as pd
|
|||
import trio
|
||||
import tractor
|
||||
|
||||
from . import watchlists as wl
|
||||
from .log import get_console_log, colorize_json, get_logger
|
||||
from .brokers import core, get_brokermod, data, config
|
||||
from .brokers.core import maybe_spawn_brokerd_as_subactor, _data_mods
|
||||
from ..cli import cli
|
||||
from .. import watchlists as wl
|
||||
from ..log import get_console_log, colorize_json, get_logger
|
||||
from ..brokers.core import maybe_spawn_brokerd_as_subactor
|
||||
from ..brokers import core, get_brokermod, data
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -132,11 +87,10 @@ def quote(config, tickers, df_output):
|
|||
brokermod.log.warn(f"Could not find symbol {ticker}?")
|
||||
|
||||
if df_output:
|
||||
cols = next(filter(bool, quotes.values())).copy()
|
||||
cols = next(filter(bool, quotes)).copy()
|
||||
cols.pop('symbol')
|
||||
df = pd.DataFrame(
|
||||
(quote or {} for quote in quotes.values()),
|
||||
index=quotes.keys(),
|
||||
(quote or {} for quote in quotes),
|
||||
columns=cols,
|
||||
)
|
||||
click.echo(df)
|
||||
|
@ -144,6 +98,41 @@ def quote(config, tickers, df_output):
|
|||
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()
|
||||
@click.option('--tl', is_flag=True, help='Enable tractor logging')
|
||||
@click.option('--rate', '-r', default=3, help='Quote rate limit')
|
||||
|
@ -184,6 +173,7 @@ def monitor(config, rate, name, dhost, test, tl):
|
|||
name='monitor',
|
||||
loglevel=loglevel if tl else None,
|
||||
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),
|
||||
name='kivy-options-chain',
|
||||
loglevel=loglevel if tl else None,
|
||||
start_method='forkserver',
|
||||
)
|
||||
|
|
|
@ -110,3 +110,15 @@ async def contracts(
|
|||
async with brokermod.get_client() as client:
|
||||
# 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)
|
||||
|
|
|
@ -570,14 +570,24 @@ class Client:
|
|||
raise SymbolNotFound(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()
|
||||
bars = await self.api.candles(
|
||||
sid,
|
||||
start=est_start.isoformat(),
|
||||
end=est_now.isoformat(),
|
||||
end=est_end.isoformat(),
|
||||
interval=_time_frames[time_frame],
|
||||
)
|
||||
log.debug(
|
||||
|
|
|
@ -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 _
|
Loading…
Reference in New Issue