Make watchlist app broker agnostic

kivy_mainline_and_py3.8
Tyler Goodlet 2018-03-21 10:44:05 -04:00
parent 6b47130c77
commit 456e86990f
1 changed files with 20 additions and 91 deletions

View File

@ -6,7 +6,7 @@ Launch with ``piker watch <watchlist name>``.
(Currently there's a bunch of questrade specific stuff in here)
"""
from itertools import chain
from functools import partial
from types import ModuleType
import trio
from kivy.uix.boxlayout import BoxLayout
@ -18,7 +18,6 @@ from kivy import utils
from kivy.app import async_runTouchApp
from kivy.core.window import Window
from ..calc import humanize, percent_change
from ..log import get_logger
from .pager import PagerView
from ..brokers.core import poll_tickers
@ -97,81 +96,6 @@ _kv = (f'''
''')
# Questrade key conversion / column order
_qt_keys = {
'symbol': 'symbol', # done manually in qtconvert
'%': '%',
'lastTradePrice': 'last',
'askPrice': 'ask',
'bidPrice': 'bid',
'lastTradeSize': 'size',
'bidSize': 'bsize',
'askSize': 'asize',
'VWAP': ('VWAP', partial(round, ndigits=3)),
'mktcap': ('mktcap', humanize),
'$ vol': ('$ vol', humanize),
'volume': ('vol', humanize),
'close': 'close',
'openPrice': 'open',
'lowPrice': 'low',
'highPrice': 'high',
'low52w': 'low52w',
'high52w': 'high52w',
# "lastTradePriceTrHrs": 7.99,
# "lastTradeTick": "Equal",
# "lastTradeTime": "2018-01-30T18:28:23.434000-05:00",
# "symbolId": 3575753,
# "tier": "",
# 'isHalted': 'halted',
# 'delay': 'delay', # as subscript 'p'
}
def qtconvert(
quote: dict, symbol_data: dict,
keymap: dict = _qt_keys,
) -> (dict, dict):
"""Remap a list of quote dicts ``quotes`` using the mapping of old keys
-> new keys ``keymap`` returning 2 dicts: one with raw data and the other
for display.
Returns 2 dicts: first is the original values mapped by new keys,
and the second is the same but with all values converted to a
"display-friendly" string format.
"""
last = quote['lastTradePrice']
symbol = quote['symbol']
previous = symbol_data[symbol]['prevDayClosePrice']
change = percent_change(previous, last)
share_count = symbol_data[symbol].get('outstandingShares', None)
mktcap = share_count * last if share_count else 'NA'
computed = {
'symbol': quote['symbol'],
'%': round(change, 3),
'mktcap': mktcap,
'$ vol': round(quote['VWAP'] * quote['volume'], 3),
'close': previous,
}
new = {}
displayable = {}
for key, new_key in keymap.items():
display_value = value = quote.get(key) or computed.get(key)
# API servers can return `None` vals when markets are closed (weekend)
value = 0 if value is None else value
# convert values to a displayble format using available formatting func
if isinstance(new_key, tuple):
new_key, func = new_key
display_value = func(value)
new[new_key] = value
displayable[new_key] = display_value
return new, displayable
class HeaderCell(Button):
"""Column header cell label.
"""
@ -267,7 +191,8 @@ class Row(GridLayout):
turn adjust the text color of the values based on content changes.
"""
def __init__(
self, record, headers=(), bidasks=None, table=None, is_header_row=False,
self, record, headers=(), bidasks=None, table=None,
is_header_row=False,
**kwargs
):
super(Row, self).__init__(cols=len(record), **kwargs)
@ -392,6 +317,7 @@ class TickerTable(GridLayout):
async def update_quotes(
brokermod: ModuleType,
widgets: dict,
queue: trio.Queue,
symbol_data: dict,
@ -425,7 +351,9 @@ async def update_quotes(
for quote in first_quotes:
sym = quote['symbol']
row = grid.symbols2rows[sym]
record, displayable = qtconvert(quote, symbol_data=symbol_data)
# record, displayable = qtconvert(quote, symbol_data=symbol_data)
record, displayable = brokermod.format_quote(
quote, symbol_data=symbol_data)
row.update(record, displayable)
color_row(row, record)
cache[sym] = (record, row)
@ -437,7 +365,9 @@ async def update_quotes(
log.debug("Waiting on quotes")
quotes = await queue.get() # new quotes data only
for quote in quotes:
record, displayable = qtconvert(quote, symbol_data=symbol_data)
# record, displayable = qtconvert(quote, symbol_data=symbol_data)
record, displayable = brokermod.format_quote(
quote, symbol_data=symbol_data)
row = grid.symbols2rows[record['symbol']]
cache[record['symbol']] = (record, row)
row.update(record, displayable)
@ -469,26 +399,24 @@ async def _async_main(name, tickers, brokermod):
# get first quotes response
pkts = await queue.get()
first_quotes = [
# qtconvert(quote, symbol_data=sd)[0] for quote in pkts]
brokermod.format_quote(quote, symbol_data=sd)[0]
for quote in pkts]
if pkts[0]['lastTradePrice'] is None:
log.error("Questrade API is down temporarily")
if first_quotes[0].get('last') is None:
log.error("Broker API is down temporarily")
nursery.cancel_scope.cancel()
return
first_quotes = [
qtconvert(quote, symbol_data=sd)[0] for quote in pkts]
# build out UI
Window.set_title(f"watchlist: {name}\t(press ? for help)")
Builder.load_string(_kv)
box = BoxLayout(orientation='vertical', padding=5, spacing=5)
# define bid-ask "stacked" cells
bidasks = {
'last': ['bid', 'ask'],
'size': ['bsize', 'asize'],
'VWAP': ['low', 'high'],
}
# (TODO: needs some rethinking and renaming for sure)
bidasks = brokermod._bidasks
# add header row
headers = first_quotes[0].keys()
@ -531,4 +459,5 @@ async def _async_main(name, tickers, brokermod):
'pager': pager,
}
nursery.start_soon(run_kivy, widgets['root'], nursery)
nursery.start_soon(update_quotes, widgets, queue, sd, pkts)
nursery.start_soon(
update_quotes, brokermod, widgets, queue, sd, pkts)