Support (sub)plot names separate from data array keys

fsp_feeds
Tyler Goodlet 2021-07-26 19:40:39 -04:00
parent 65158b8c64
commit 825680b8c6
1 changed files with 97 additions and 32 deletions

View File

@ -299,13 +299,17 @@ class LinkedSplits(QtWidgets.QWidget):
def set_split_sizes( def set_split_sizes(
self, self,
prop: float = 0.375 # proportion allocated to consumer subcharts prop: float = 0.375 # proportion allocated to consumer subcharts
) -> None: ) -> None:
"""Set the proportion of space allocated for linked subcharts. '''Set the proportion of space allocated for linked subcharts.
"""
'''
major = 1 - prop major = 1 - prop
min_h_ind = int((self.height() * prop) / len(self.subplots)) min_h_ind = int((self.height() * prop) / len(self.subplots))
sizes = [int(self.height() * major)] sizes = [int(self.height() * major)]
sizes.extend([min_h_ind] * len(self.subplots)) sizes.extend([min_h_ind] * len(self.subplots))
self.splitter.setSizes(sizes) # , int(self.height()*0.2) self.splitter.setSizes(sizes) # , int(self.height()*0.2)
def focus(self) -> None: def focus(self) -> None:
@ -318,9 +322,12 @@ class LinkedSplits(QtWidgets.QWidget):
def plot_ohlc_main( def plot_ohlc_main(
self, self,
symbol: Symbol, symbol: Symbol,
array: np.ndarray, array: np.ndarray,
style: str = 'bar', style: str = 'bar',
) -> 'ChartPlotWidget': ) -> 'ChartPlotWidget':
"""Start up and show main (price) chart and all linked subcharts. """Start up and show main (price) chart and all linked subcharts.
@ -352,17 +359,23 @@ class LinkedSplits(QtWidgets.QWidget):
def add_plot( def add_plot(
self, self,
name: str, name: str,
array: np.ndarray, array: np.ndarray,
xaxis: DynamicDateAxis = None,
array_key: Optional[str] = None,
# xaxis: Optional[DynamicDateAxis] = None,
style: str = 'line', style: str = 'line',
_is_main: bool = False, _is_main: bool = False,
**cpw_kwargs, **cpw_kwargs,
) -> 'ChartPlotWidget': ) -> 'ChartPlotWidget':
"""Add (sub)plots to chart widget by name. '''Add (sub)plots to chart widget by name.
If ``name`` == ``"main"`` the chart will be the the primary view. If ``name`` == ``"main"`` the chart will be the the primary view.
"""
'''
if self.chart is None and not _is_main: if self.chart is None and not _is_main:
raise RuntimeError( raise RuntimeError(
"A main plot must be created first with `.plot_ohlc_main()`") "A main plot must be created first with `.plot_ohlc_main()`")
@ -372,17 +385,25 @@ class LinkedSplits(QtWidgets.QWidget):
cv.linkedsplits = self cv.linkedsplits = self
# use "indicator axis" by default # use "indicator axis" by default
if xaxis is None:
xaxis = DynamicDateAxis( # TODO: we gotta possibly assign this back
orientation='bottom', # to the last subplot on removal of some last subplot
linkedsplits=self
) xaxis = DynamicDateAxis(
orientation='bottom',
linkedsplits=self
)
if self.xaxis:
self.xaxis.hide()
self.xaxis = xaxis
cpw = ChartPlotWidget( cpw = ChartPlotWidget(
# this name will be used to register the primary # this name will be used to register the primary
# graphics curve managed by the subchart # graphics curve managed by the subchart
name=name, name=name,
data_key=array_key or name,
array=array, array=array,
parent=self.splitter, parent=self.splitter,
@ -395,7 +416,6 @@ class LinkedSplits(QtWidgets.QWidget):
viewBox=cv, viewBox=cv,
**cpw_kwargs, **cpw_kwargs,
) )
print(f'xaxis ps: {xaxis.pos()}')
# give viewbox as reference to chart # give viewbox as reference to chart
# allowing for kb controls and interactions on **this** widget # allowing for kb controls and interactions on **this** widget
@ -416,10 +436,10 @@ class LinkedSplits(QtWidgets.QWidget):
# draw curve graphics # draw curve graphics
if style == 'bar': if style == 'bar':
cpw.draw_ohlc(name, array) cpw.draw_ohlc(name, array, array_key=array_key)
elif style == 'line': elif style == 'line':
cpw.draw_curve(name, array) cpw.draw_curve(name, array, array_key=array_key)
else: else:
raise ValueError(f"Chart style {style} is currently unsupported") raise ValueError(f"Chart style {style} is currently unsupported")
@ -428,11 +448,12 @@ class LinkedSplits(QtWidgets.QWidget):
# track by name # track by name
self.subplots[name] = cpw self.subplots[name] = cpw
# XXX: we need this right?
self.splitter.addWidget(cpw)
# scale split regions # scale split regions
self.set_split_sizes() self.set_split_sizes()
# XXX: we need this right?
# self.splitter.addWidget(cpw)
else: else:
assert style == 'bar', 'main chart must be OHLC' assert style == 'bar', 'main chart must be OHLC'
@ -465,9 +486,11 @@ class ChartPlotWidget(pg.PlotWidget):
def __init__( def __init__(
self, self,
# the data view we generate graphics from
# the "data view" we generate graphics from
name: str, name: str,
array: np.ndarray, array: np.ndarray,
data_key: str,
linkedsplits: LinkedSplits, linkedsplits: LinkedSplits,
view_color: str = 'papas_special', view_color: str = 'papas_special',
@ -491,6 +514,7 @@ class ChartPlotWidget(pg.PlotWidget):
**kwargs **kwargs
) )
self.name = name self.name = name
self.data_key = data_key
self.linked = linkedsplits self.linked = linkedsplits
# scene-local placeholder for book graphics # scene-local placeholder for book graphics
@ -618,8 +642,12 @@ class ChartPlotWidget(pg.PlotWidget):
def draw_ohlc( def draw_ohlc(
self, self,
name: str, name: str,
data: np.ndarray, data: np.ndarray,
array_key: Optional[str] = None,
) -> pg.GraphicsObject: ) -> pg.GraphicsObject:
""" """
Draw OHLC datums to chart. Draw OHLC datums to chart.
@ -637,7 +665,8 @@ class ChartPlotWidget(pg.PlotWidget):
# draw after to allow self.scene() to work... # draw after to allow self.scene() to work...
graphics.draw_from_data(data) graphics.draw_from_data(data)
self._graphics[name] = graphics data_key = array_key or name
self._graphics[data_key] = graphics
self.linked.cursor.contents_labels.add_label( self.linked.cursor.contents_labels.add_label(
self, self,
@ -652,12 +681,17 @@ class ChartPlotWidget(pg.PlotWidget):
def draw_curve( def draw_curve(
self, self,
name: str, name: str,
data: np.ndarray, data: np.ndarray,
array_key: Optional[str] = None,
overlay: bool = False, overlay: bool = False,
color: str = 'default_light', color: str = 'default_light',
add_label: bool = True, add_label: bool = True,
**pdi_kwargs, **pdi_kwargs,
) -> pg.PlotDataItem: ) -> pg.PlotDataItem:
"""Draw a "curve" (line plot graphics) for the provided data in """Draw a "curve" (line plot graphics) for the provided data in
the input array ``data``. the input array ``data``.
@ -668,10 +702,11 @@ class ChartPlotWidget(pg.PlotWidget):
} }
pdi_kwargs.update(_pdi_defaults) pdi_kwargs.update(_pdi_defaults)
data_key = array_key or name
# curve = pg.PlotDataItem( # curve = pg.PlotDataItem(
# curve = pg.PlotCurveItem( # curve = pg.PlotCurveItem(
curve = FastAppendCurve( curve = FastAppendCurve(
y=data[name], y=data[data_key],
x=data['index'], x=data['index'],
# antialias=True, # antialias=True,
name=name, name=name,
@ -730,8 +765,10 @@ class ChartPlotWidget(pg.PlotWidget):
def _add_sticky( def _add_sticky(
self, self,
name: str, name: str,
bg_color='bracket', bg_color='bracket',
) -> YAxisLabel: ) -> YAxisLabel:
# if the sticky is for our symbol # if the sticky is for our symbol
@ -769,18 +806,23 @@ class ChartPlotWidget(pg.PlotWidget):
def update_curve_from_array( def update_curve_from_array(
self, self,
name: str, name: str,
array: np.ndarray, array: np.ndarray,
array_key: Optional[str] = None,
**kwargs, **kwargs,
) -> pg.GraphicsObject: ) -> pg.GraphicsObject:
"""Update the named internal graphics from ``array``. """Update the named internal graphics from ``array``.
""" """
data_key = array_key or name
if name not in self._overlays: if name not in self._overlays:
self._arrays['ohlc'] = array self._arrays['ohlc'] = array
else: else:
self._arrays[name] = array self._arrays[data_key] = array
curve = self._graphics[name] curve = self._graphics[name]
@ -790,7 +832,7 @@ class ChartPlotWidget(pg.PlotWidget):
# one place to dig around this might be the `QBackingStore` # one place to dig around this might be the `QBackingStore`
# https://doc.qt.io/qt-5/qbackingstore.html # https://doc.qt.io/qt-5/qbackingstore.html
# curve.setData(y=array[name], x=array['index'], **kwargs) # curve.setData(y=array[name], x=array['index'], **kwargs)
curve.update_from_array(x=array['index'], y=array[name], **kwargs) curve.update_from_array(x=array['index'], y=array[data_key], **kwargs)
return curve return curve
@ -1061,8 +1103,7 @@ async def chart_from_quotes(
if wap_in_history: if wap_in_history:
# update vwap overlay line # update vwap overlay line
chart.update_curve_from_array( chart.update_curve_from_array('bar_wap', ohlcv.array)
'bar_wap', ohlcv.array)
# l1 book events # l1 book events
# throttle the book graphics updates at a lower rate # throttle the book graphics updates at a lower rate
@ -1167,9 +1208,9 @@ async def spawn_fsps(
# Currently we spawn an actor per fsp chain but # Currently we spawn an actor per fsp chain but
# likely we'll want to pool them eventually to # likely we'll want to pool them eventually to
# scale horizonatlly once cores are used up. # scale horizonatlly once cores are used up.
for fsp_func_name, conf in fsps.items(): for display_name, conf in fsps.items():
display_name = f'fsp.{fsp_func_name}' fsp_func_name = conf['fsp_func_name']
# TODO: load function here and introspect # TODO: load function here and introspect
# return stream type(s) # return stream type(s)
@ -1177,7 +1218,7 @@ async def spawn_fsps(
# TODO: should `index` be a required internal field? # TODO: should `index` be a required internal field?
fsp_dtype = np.dtype([('index', int), (fsp_func_name, float)]) fsp_dtype = np.dtype([('index', int), (fsp_func_name, float)])
key = f'{sym}.' + display_name key = f'{sym}.fsp.' + display_name
# this is all sync currently # this is all sync currently
shm, opened = maybe_open_shm_array( shm, opened = maybe_open_shm_array(
@ -1195,7 +1236,7 @@ async def spawn_fsps(
portal = await n.start_actor( portal = await n.start_actor(
enable_modules=['piker.fsp'], enable_modules=['piker.fsp'],
name=display_name, name='fsp.' + display_name,
) )
# init async # init async
@ -1234,7 +1275,7 @@ async def run_fsp(
config map. config map.
""" """
done = linkedsplits.window().status_bar.open_status( done = linkedsplits.window().status_bar.open_status(
f'loading {display_name}..', f'loading fsp, {display_name}..',
group_key=group_status_key, group_key=group_status_key,
) )
@ -1270,9 +1311,11 @@ async def run_fsp(
else: else:
chart = linkedsplits.add_plot( chart = linkedsplits.add_plot(
name=fsp_func_name, name=display_name,
array=shm.array, array=shm.array,
array_key=conf['fsp_func_name'],
# curve by default # curve by default
ohlc=False, ohlc=False,
@ -1294,14 +1337,19 @@ async def run_fsp(
# read from last calculated value # read from last calculated value
array = shm.array array = shm.array
# XXX: fsp func names are unique meaning we don't have
# duplicates of the underlying data even if multiple
# sub-charts reference it under different 'named charts'.
value = array[fsp_func_name][-1] value = array[fsp_func_name][-1]
last_val_sticky.update_from_data(-1, value) last_val_sticky.update_from_data(-1, value)
chart.linked.focus() chart.linked.focus()
# works also for overlays in which case data is looked up from # works also for overlays in which case data is looked up from
# internal chart array set.... # internal chart array set....
chart.update_curve_from_array(fsp_func_name, shm.array) chart.update_curve_from_array(display_name, shm.array, array_key=fsp_func_name)
# TODO: figure out if we can roll our own `FillToThreshold` to # TODO: figure out if we can roll our own `FillToThreshold` to
# get brush filled polygons for OS/OB conditions. # get brush filled polygons for OS/OB conditions.
@ -1365,7 +1413,11 @@ async def run_fsp(
last_val_sticky.update_from_data(-1, value) last_val_sticky.update_from_data(-1, value)
# update graphics # update graphics
chart.update_curve_from_array(fsp_func_name, array) chart.update_curve_from_array(
display_name,
array,
array_key=fsp_func_name,
)
# set time of last graphics update # set time of last graphics update
last = now last = now
@ -1420,7 +1472,11 @@ async def check_for_new_bars(feed, ohlcv, linkedsplits):
) )
for name, chart in linkedsplits.subplots.items(): for name, chart in linkedsplits.subplots.items():
chart.update_curve_from_array(chart.name, chart._shm.array) chart.update_curve_from_array(
chart.name,
chart._shm.array,
array_key=chart.data_key
)
# shift the view if in follow mode # shift the view if in follow mode
price_chart.increment_view() price_chart.increment_view()
@ -1510,6 +1566,14 @@ async def display_symbol_data(
# TODO: eventually we'll support some kind of n-compose syntax # TODO: eventually we'll support some kind of n-compose syntax
fsp_conf = { fsp_conf = {
'rsi': { 'rsi': {
'fsp_func_name': 'rsi',
'period': 14,
'chart_kwargs': {
'static_yrange': (0, 100),
},
},
'rsi2': {
'fsp_func_name': 'rsi',
'period': 14, 'period': 14,
'chart_kwargs': { 'chart_kwargs': {
'static_yrange': (0, 100), 'static_yrange': (0, 100),
@ -1532,6 +1596,7 @@ async def display_symbol_data(
else: else:
fsp_conf.update({ fsp_conf.update({
'vwap': { 'vwap': {
'fsp_func_name': 'vwap',
'overlay': True, 'overlay': True,
'anchor': 'session', 'anchor': 'session',
}, },
@ -1723,7 +1788,7 @@ async def _async_main(
'allocator': { 'allocator': {
'key': '**allocator**:', 'key': '**allocator**:',
'type': 'select', 'type': 'select',
'default_value': ['$ size', '% of port',], 'default_value': ['$ size', '% of port', '# shares'],
}, },
'dollar_size': { 'dollar_size': {
'key': '**$size**:', 'key': '**$size**:',