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.
|
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',
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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