Make watchlist app broker agnostic
parent
6b47130c77
commit
456e86990f
|
@ -6,7 +6,7 @@ Launch with ``piker watch <watchlist name>``.
|
||||||
(Currently there's a bunch of questrade specific stuff in here)
|
(Currently there's a bunch of questrade specific stuff in here)
|
||||||
"""
|
"""
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from functools import partial
|
from types import ModuleType
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
|
@ -18,7 +18,6 @@ from kivy import utils
|
||||||
from kivy.app import async_runTouchApp
|
from kivy.app import async_runTouchApp
|
||||||
from kivy.core.window import Window
|
from kivy.core.window import Window
|
||||||
|
|
||||||
from ..calc import humanize, percent_change
|
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from .pager import PagerView
|
from .pager import PagerView
|
||||||
from ..brokers.core import poll_tickers
|
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):
|
class HeaderCell(Button):
|
||||||
"""Column header cell label.
|
"""Column header cell label.
|
||||||
"""
|
"""
|
||||||
|
@ -267,7 +191,8 @@ class Row(GridLayout):
|
||||||
turn adjust the text color of the values based on content changes.
|
turn adjust the text color of the values based on content changes.
|
||||||
"""
|
"""
|
||||||
def __init__(
|
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
|
**kwargs
|
||||||
):
|
):
|
||||||
super(Row, self).__init__(cols=len(record), **kwargs)
|
super(Row, self).__init__(cols=len(record), **kwargs)
|
||||||
|
@ -392,6 +317,7 @@ class TickerTable(GridLayout):
|
||||||
|
|
||||||
|
|
||||||
async def update_quotes(
|
async def update_quotes(
|
||||||
|
brokermod: ModuleType,
|
||||||
widgets: dict,
|
widgets: dict,
|
||||||
queue: trio.Queue,
|
queue: trio.Queue,
|
||||||
symbol_data: dict,
|
symbol_data: dict,
|
||||||
|
@ -425,7 +351,9 @@ async def update_quotes(
|
||||||
for quote in first_quotes:
|
for quote in first_quotes:
|
||||||
sym = quote['symbol']
|
sym = quote['symbol']
|
||||||
row = grid.symbols2rows[sym]
|
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)
|
row.update(record, displayable)
|
||||||
color_row(row, record)
|
color_row(row, record)
|
||||||
cache[sym] = (record, row)
|
cache[sym] = (record, row)
|
||||||
|
@ -437,7 +365,9 @@ async def update_quotes(
|
||||||
log.debug("Waiting on quotes")
|
log.debug("Waiting on quotes")
|
||||||
quotes = await queue.get() # new quotes data only
|
quotes = await queue.get() # new quotes data only
|
||||||
for quote in quotes:
|
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']]
|
row = grid.symbols2rows[record['symbol']]
|
||||||
cache[record['symbol']] = (record, row)
|
cache[record['symbol']] = (record, row)
|
||||||
row.update(record, displayable)
|
row.update(record, displayable)
|
||||||
|
@ -469,26 +399,24 @@ async def _async_main(name, tickers, brokermod):
|
||||||
|
|
||||||
# get first quotes response
|
# get first quotes response
|
||||||
pkts = await queue.get()
|
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:
|
if first_quotes[0].get('last') is None:
|
||||||
log.error("Questrade API is down temporarily")
|
log.error("Broker API is down temporarily")
|
||||||
nursery.cancel_scope.cancel()
|
nursery.cancel_scope.cancel()
|
||||||
return
|
return
|
||||||
|
|
||||||
first_quotes = [
|
|
||||||
qtconvert(quote, symbol_data=sd)[0] for quote in pkts]
|
|
||||||
|
|
||||||
# build out UI
|
# build out UI
|
||||||
Window.set_title(f"watchlist: {name}\t(press ? for help)")
|
Window.set_title(f"watchlist: {name}\t(press ? for help)")
|
||||||
Builder.load_string(_kv)
|
Builder.load_string(_kv)
|
||||||
box = BoxLayout(orientation='vertical', padding=5, spacing=5)
|
box = BoxLayout(orientation='vertical', padding=5, spacing=5)
|
||||||
|
|
||||||
# define bid-ask "stacked" cells
|
# define bid-ask "stacked" cells
|
||||||
bidasks = {
|
# (TODO: needs some rethinking and renaming for sure)
|
||||||
'last': ['bid', 'ask'],
|
bidasks = brokermod._bidasks
|
||||||
'size': ['bsize', 'asize'],
|
|
||||||
'VWAP': ['low', 'high'],
|
|
||||||
}
|
|
||||||
|
|
||||||
# add header row
|
# add header row
|
||||||
headers = first_quotes[0].keys()
|
headers = first_quotes[0].keys()
|
||||||
|
@ -531,4 +459,5 @@ async def _async_main(name, tickers, brokermod):
|
||||||
'pager': pager,
|
'pager': pager,
|
||||||
}
|
}
|
||||||
nursery.start_soon(run_kivy, widgets['root'], nursery)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue