Support (sub)plot names separate from data array keys
parent
65158b8c64
commit
825680b8c6
|
@ -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**:',
|
||||||
|
|
Loading…
Reference in New Issue