2018-01-26 01:54:13 +00:00
|
|
|
"""
|
|
|
|
Console interface to broker client/daemons.
|
|
|
|
"""
|
2018-01-26 19:31:15 +00:00
|
|
|
from functools import partial
|
2018-03-21 04:52:06 +00:00
|
|
|
from importlib import import_module
|
2018-03-23 20:47:09 +00:00
|
|
|
import os
|
2018-03-21 04:52:06 +00:00
|
|
|
from collections import defaultdict
|
|
|
|
import json
|
2018-01-26 19:31:15 +00:00
|
|
|
|
2018-01-26 01:54:13 +00:00
|
|
|
import click
|
|
|
|
import trio
|
2018-02-08 07:26:55 +00:00
|
|
|
import pandas as pd
|
2018-01-26 19:31:15 +00:00
|
|
|
|
2018-03-27 18:06:26 +00:00
|
|
|
|
2018-03-20 17:28:24 +00:00
|
|
|
from .log import get_console_log, colorize_json, get_logger
|
2018-03-27 18:06:26 +00:00
|
|
|
from . import watchlists as wl
|
2018-03-29 00:43:33 +00:00
|
|
|
from .brokers import core, get_brokermod
|
2018-03-20 17:28:24 +00:00
|
|
|
|
|
|
|
log = get_logger('cli')
|
2018-03-21 21:28:40 +00:00
|
|
|
DEFAULT_BROKER = 'robinhood'
|
2018-01-26 01:54:13 +00:00
|
|
|
|
2018-03-21 04:52:06 +00:00
|
|
|
_config_dir = click.get_app_dir('piker')
|
2018-03-23 20:47:09 +00:00
|
|
|
_watchlists_data_path = os.path.join(_config_dir, 'watchlists.json')
|
2018-01-26 01:54:13 +00:00
|
|
|
|
2018-04-03 03:55:02 +00:00
|
|
|
|
2018-01-26 19:31:15 +00:00
|
|
|
def run(main, loglevel='info'):
|
2018-01-26 01:54:13 +00:00
|
|
|
log = get_console_log(loglevel)
|
|
|
|
|
2018-01-26 19:31:15 +00:00
|
|
|
# main sandwich
|
2018-01-26 01:54:13 +00:00
|
|
|
try:
|
2018-01-26 19:31:15 +00:00
|
|
|
return trio.run(main)
|
2018-01-26 01:54:13 +00:00
|
|
|
except Exception as err:
|
|
|
|
log.exception(err)
|
2018-01-26 19:31:15 +00:00
|
|
|
finally:
|
2018-01-27 06:52:00 +00:00
|
|
|
log.debug("Exiting piker")
|
2018-01-26 01:54:13 +00:00
|
|
|
|
|
|
|
|
2018-01-26 19:31:15 +00:00
|
|
|
@click.group()
|
|
|
|
def cli():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
2018-03-21 21:28:40 +00:00
|
|
|
@click.option('--broker', '-b', default=DEFAULT_BROKER,
|
2018-03-21 01:02:59 +00:00
|
|
|
help='Broker backend to use')
|
2018-01-26 19:31:15 +00:00
|
|
|
@click.option('--loglevel', '-l', default='warning', help='Logging level')
|
2018-02-08 07:26:55 +00:00
|
|
|
@click.option('--keys', '-k', multiple=True,
|
|
|
|
help='Return results only for these keys')
|
2018-01-26 19:31:15 +00:00
|
|
|
@click.argument('meth', nargs=1)
|
2018-01-27 06:52:00 +00:00
|
|
|
@click.argument('kwargs', nargs=-1)
|
2018-02-08 07:26:55 +00:00
|
|
|
def api(meth, kwargs, loglevel, broker, keys):
|
2018-01-27 06:52:00 +00:00
|
|
|
"""client for testing broker API methods with pretty printing of output.
|
2018-01-26 19:31:15 +00:00
|
|
|
"""
|
|
|
|
log = get_console_log(loglevel)
|
2018-03-27 20:27:30 +00:00
|
|
|
brokermod = get_brokermod(broker)
|
2018-01-26 19:31:15 +00:00
|
|
|
|
|
|
|
_kwargs = {}
|
|
|
|
for kwarg in kwargs:
|
|
|
|
if '=' not in kwarg:
|
|
|
|
log.error(f"kwarg `{kwarg}` must be of form <key>=<value>")
|
|
|
|
else:
|
|
|
|
key, _, value = kwarg.partition('=')
|
|
|
|
_kwargs[key] = value
|
|
|
|
|
2018-03-21 01:02:59 +00:00
|
|
|
data = run(
|
|
|
|
partial(core.api, brokermod, meth, **_kwargs), loglevel=loglevel)
|
2018-02-08 07:26:55 +00:00
|
|
|
|
|
|
|
if keys:
|
|
|
|
# filter to requested keys
|
|
|
|
filtered = []
|
|
|
|
if meth in data: # often a list of dicts
|
|
|
|
for item in data[meth]:
|
|
|
|
filtered.append({key: item[key] for key in keys})
|
|
|
|
|
|
|
|
else: # likely just a dict
|
|
|
|
filtered.append({key: data[key] for key in keys})
|
|
|
|
data = filtered
|
|
|
|
|
|
|
|
click.echo(colorize_json(data))
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
2018-03-21 21:28:40 +00:00
|
|
|
@click.option('--broker', '-b', default=DEFAULT_BROKER,
|
2018-03-21 01:02:59 +00:00
|
|
|
help='Broker backend to use')
|
2018-02-08 07:26:55 +00:00
|
|
|
@click.option('--loglevel', '-l', default='warning', help='Logging level')
|
|
|
|
@click.option('--df-output', '-df', flag_value=True,
|
|
|
|
help='Ouput in `pandas.DataFrame` format')
|
2018-03-27 20:27:30 +00:00
|
|
|
@click.argument('tickers', nargs=-1, required=True)
|
2018-02-08 07:26:55 +00:00
|
|
|
def quote(loglevel, broker, tickers, df_output):
|
|
|
|
"""client for testing broker API methods with pretty printing of output.
|
|
|
|
"""
|
2018-03-27 20:27:30 +00:00
|
|
|
brokermod = get_brokermod(broker)
|
2018-03-20 17:28:24 +00:00
|
|
|
quotes = run(partial(core.quote, brokermod, tickers), loglevel=loglevel)
|
|
|
|
if not quotes:
|
|
|
|
log.error(f"No quotes could be found for {tickers}?")
|
|
|
|
return
|
2018-03-21 01:02:59 +00:00
|
|
|
|
|
|
|
cols = next(filter(bool, quotes.values())).copy()
|
2018-02-08 07:26:55 +00:00
|
|
|
cols.pop('symbol')
|
|
|
|
if df_output:
|
|
|
|
df = pd.DataFrame(
|
2018-03-21 01:02:59 +00:00
|
|
|
(quote or {} for quote in quotes.values()),
|
|
|
|
index=quotes.keys(),
|
2018-02-08 07:26:55 +00:00
|
|
|
columns=cols,
|
|
|
|
)
|
|
|
|
click.echo(df)
|
|
|
|
else:
|
2018-02-09 00:15:21 +00:00
|
|
|
click.echo(colorize_json(quotes))
|
2018-01-29 17:45:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
2018-03-21 21:28:40 +00:00
|
|
|
@click.option('--broker', '-b', default=DEFAULT_BROKER,
|
2018-03-21 01:02:59 +00:00
|
|
|
help='Broker backend to use')
|
2018-02-09 08:29:10 +00:00
|
|
|
@click.option('--loglevel', '-l', default='warning', help='Logging level')
|
2018-03-21 21:28:40 +00:00
|
|
|
@click.option('--rate', '-r', default=5, help='Logging level')
|
2018-03-14 18:00:24 +00:00
|
|
|
@click.argument('name', nargs=1, required=True)
|
2018-03-21 21:28:40 +00:00
|
|
|
def watch(loglevel, broker, rate, name):
|
2018-02-09 08:29:10 +00:00
|
|
|
"""Spawn a watchlist.
|
|
|
|
"""
|
|
|
|
from .ui.watchlist import _async_main
|
2018-03-21 21:28:40 +00:00
|
|
|
log = get_console_log(loglevel) # activate console logging
|
2018-03-27 20:27:30 +00:00
|
|
|
brokermod = get_brokermod(broker)
|
2018-02-09 08:29:10 +00:00
|
|
|
|
|
|
|
watchlists = {
|
|
|
|
'cannabis': [
|
|
|
|
'EMH.VN', 'LEAF.TO', 'HVT.VN', 'HMMJ.TO', 'APH.TO',
|
|
|
|
'CBW.VN', 'TRST.CN', 'VFF.TO', 'ACB.TO', 'ABCN.VN',
|
|
|
|
'APH.TO', 'MARI.CN', 'WMD.VN', 'LEAF.TO', 'THCX.VN',
|
|
|
|
'WEED.TO', 'NINE.VN', 'RTI.VN', 'SNN.CN', 'ACB.TO',
|
2018-03-28 18:00:17 +00:00
|
|
|
'OGI.VN', 'IMH.VN', 'FIRE.VN', 'EAT.CN',
|
2018-03-08 04:50:47 +00:00
|
|
|
'WMD.VN', 'HEMP.VN', 'CALI.CN', 'RQB.CN', 'MPX.CN',
|
|
|
|
'SEED.TO', 'HMJR.TO', 'CMED.TO', 'PAS.VN',
|
|
|
|
'CRON',
|
|
|
|
],
|
2018-03-21 21:28:40 +00:00
|
|
|
'dad': ['GM', 'TSLA', 'DOL.TO', 'CIM', 'SPY', 'SHOP.TO'],
|
|
|
|
'pharma': ['ATE.VN'],
|
|
|
|
'indexes': ['SPY', 'DAX', 'QQQ', 'DIA'],
|
2018-02-09 08:29:10 +00:00
|
|
|
}
|
|
|
|
# broker_conf_path = os.path.join(
|
|
|
|
# click.get_app_dir('piker'), 'watchlists.json')
|
|
|
|
# from piker.testing import _quote_streamer as brokermod
|
2018-03-21 21:28:40 +00:00
|
|
|
broker_limit = getattr(brokermod, '_rate_limit', float('inf'))
|
|
|
|
if broker_limit < rate:
|
|
|
|
rate = broker_limit
|
|
|
|
log.warn(f"Limiting {brokermod.__name__} query rate to {rate}/sec")
|
|
|
|
trio.run(_async_main, name, watchlists[name], brokermod, rate)
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
# broker_conf_path = os.path.join(
|
|
|
|
# click.get_app_dir('piker'), 'watchlists.json')
|
|
|
|
# from piker.testing import _quote_streamer as brokermod
|
|
|
|
trio.run(_async_main, name, watchlists[name], brokermod)
|
|
|
|
|
|
|
|
|
|
|
|
@cli.group()
|
|
|
|
@click.option('--loglevel', '-l', default='warning', help='Logging level')
|
2018-04-03 03:55:02 +00:00
|
|
|
@click.option('--config_dir', '-d', default=_watchlists_data_path,
|
|
|
|
help='Path to piker configuration directory')
|
2018-03-21 04:52:06 +00:00
|
|
|
@click.pass_context
|
2018-04-03 03:55:02 +00:00
|
|
|
def watchlists(ctx, loglevel, config_dir):
|
2018-03-31 03:48:43 +00:00
|
|
|
"""Watchlists commands and operations
|
2018-03-21 04:52:06 +00:00
|
|
|
"""
|
|
|
|
get_console_log(loglevel) # activate console logging
|
2018-03-27 18:06:26 +00:00
|
|
|
wl.make_config_dir(_config_dir)
|
2018-04-03 03:55:02 +00:00
|
|
|
ctx.obj = {'path': config_dir,
|
|
|
|
'watchlist': wl.ensure_watchlists(config_dir)}
|
2018-03-21 04:52:06 +00:00
|
|
|
|
2018-03-23 20:47:09 +00:00
|
|
|
|
2018-03-21 04:52:06 +00:00
|
|
|
@watchlists.command(help='show watchlist')
|
|
|
|
@click.argument('name', nargs=1, required=False)
|
|
|
|
@click.pass_context
|
|
|
|
def show(ctx, name):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
2018-03-21 04:52:06 +00:00
|
|
|
click.echo(colorize_json(
|
2018-03-31 03:48:43 +00:00
|
|
|
watchlist if name is None else watchlist[name]))
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
|
2018-03-27 18:06:26 +00:00
|
|
|
@watchlists.command(help='load passed in watchlist')
|
|
|
|
@click.argument('data', nargs=1, required=True)
|
|
|
|
@click.pass_context
|
|
|
|
def load(ctx, data):
|
|
|
|
try:
|
2018-04-03 03:55:02 +00:00
|
|
|
wl.write_watchlists(data, ctx.obj['path'])
|
2018-03-27 18:06:26 +00:00
|
|
|
except (json.JSONDecodeError, IndexError):
|
2018-03-31 03:48:43 +00:00
|
|
|
click.echo('You have passed an invalid text respresentation of a '
|
|
|
|
'JSON object. Try again.')
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
@watchlists.command(help='add ticker to watchlist')
|
|
|
|
@click.argument('name', nargs=1, required=True)
|
|
|
|
@click.argument('ticker_name', nargs=1, required=True)
|
|
|
|
@click.pass_context
|
|
|
|
def add(ctx, name, ticker_name):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
|
|
|
wl.add_ticker(name, ticker_name, watchlist, ctx.obj['path'])
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
@watchlists.command(help='remove ticker from watchlist')
|
|
|
|
@click.argument('name', nargs=1, required=True)
|
|
|
|
@click.argument('ticker_name', nargs=1, required=True)
|
|
|
|
@click.pass_context
|
|
|
|
def remove(ctx, name, ticker_name):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
|
|
|
wl.remove_ticker(name, ticker_name, watchlist, ctx.obj['path'])
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
|
2018-03-31 03:48:43 +00:00
|
|
|
@watchlists.command(help='delete watchlist group')
|
2018-03-21 04:52:06 +00:00
|
|
|
@click.argument('name', nargs=1, required=True)
|
|
|
|
@click.pass_context
|
|
|
|
def delete(ctx, name):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
|
|
|
wl.delete_group(name, watchlist, ctx.obj['path'])
|
2018-03-21 04:52:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
@watchlists.command(help='merge a watchlist from another user')
|
|
|
|
@click.argument('watchlist_to_merge', nargs=1, required=True)
|
|
|
|
@click.pass_context
|
|
|
|
def merge(ctx, watchlist_to_merge):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
|
|
|
wl.merge_watchlist(watchlist_to_merge, watchlist, ctx.obj['path'])
|
2018-03-23 20:47:09 +00:00
|
|
|
|
2018-03-21 04:52:06 +00:00
|
|
|
|
2018-03-31 03:48:43 +00:00
|
|
|
@watchlists.command(help='dump text respresentation of a watchlist to console')
|
2018-03-21 04:52:06 +00:00
|
|
|
@click.argument('name', nargs=1, required=False)
|
|
|
|
@click.pass_context
|
|
|
|
def dump(ctx, name):
|
2018-04-03 03:55:02 +00:00
|
|
|
watchlist = ctx.obj['watchlist']
|
2018-03-31 03:48:43 +00:00
|
|
|
click.echo(json.dumps(watchlist))
|