Initial chart widget adjustments for agg feeds

Main "public" API change is to make `GodWidget.get/set_chart_symbol()`
accept and cache-on fqsn tuples to allow handling overlayed chart groups
and adjust method names to be plural to match.

Wrt `LinkedSplits`,
- create all chart widget axes with a `None` plotitem argument and set
  the `.pi` field after axis creation (since apparently we have another
  object reference causality dilemma..)
- set a monkeyed `PlotItem.chart_widget` for use in axes that still need
  the widget reference.
- drop feed pause/resume for now since it's leaking feed tasks on the
  `brokerd` side and we probably don't really need it any more, and if
  we still do it should be done on the feed not the flume.

Wrt `ChartPlotItem`,
- drop `._add_sticky()` and use the `Axis` method instead and add some
  overlay + axis sanity checks.
- refactor `.draw_ohlc()` to be a lighter wrapper around a call to
  `.add_plot()`.
axis_sticky_api
Tyler Goodlet 2022-11-14 16:25:19 -05:00
parent 9217610734
commit 00be100e71
1 changed files with 126 additions and 124 deletions

View File

@ -45,7 +45,6 @@ import trio
from ._axes import (
DynamicDateAxis,
PriceAxis,
YAxisLabel,
)
from ._cursor import (
Cursor,
@ -168,18 +167,18 @@ class GodWidget(QWidget):
# self.strategy_box = StrategyBoxWidget(self)
# self.toolbar_layout.addWidget(self.strategy_box)
def set_chart_symbol(
def set_chart_symbols(
self,
symbol_key: str, # of form <fqsn>.<providername>
group_key: tuple[str], # of form <fqsn>.<providername>
all_linked: tuple[LinkedSplits, LinkedSplits], # type: ignore
) -> None:
# re-sort org cache symbol list in LIFO order
cache = self._chart_cache
cache.pop(symbol_key, None)
cache[symbol_key] = all_linked
cache.pop(group_key, None)
cache[group_key] = all_linked
def get_chart_symbol(
def get_chart_symbols(
self,
symbol_key: str,
@ -188,8 +187,7 @@ class GodWidget(QWidget):
async def load_symbols(
self,
providername: str,
symbol_keys: list[str],
fqsns: list[str],
loglevel: str,
reset: bool = False,
@ -200,20 +198,11 @@ class GodWidget(QWidget):
Expects a ``numpy`` structured array containing all the ohlcv fields.
'''
fqsns: list[str] = []
# our symbol key style is always lower case
for key in list(map(str.lower, symbol_keys)):
# fully qualified symbol name (SNS i guess is what we're making?)
fqsn = '.'.join([key, providername])
fqsns.append(fqsn)
# NOTE: for now we use the first symbol in the set as the "key"
# for the overlay of feeds on the chart.
group_key = fqsns[0]
group_key: tuple[str] = tuple(fqsns)
all_linked = self.get_chart_symbol(group_key)
all_linked = self.get_chart_symbols(group_key)
order_mode_started = trio.Event()
if not self.vbox.isEmpty():
@ -245,7 +234,6 @@ class GodWidget(QWidget):
self._root_n.start_soon(
display_symbol_data,
self,
providername,
fqsns,
loglevel,
order_mode_started,
@ -253,8 +241,8 @@ class GodWidget(QWidget):
# self.vbox.addWidget(hist_charts)
self.vbox.addWidget(rt_charts)
self.set_chart_symbol(
fqsn,
self.set_chart_symbols(
group_key,
(hist_charts, rt_charts),
)
@ -568,12 +556,10 @@ class LinkedSplits(QWidget):
# be no distinction since we will have multiple symbols per
# view as part of "aggregate feeds".
self.chart = self.add_plot(
name=symbol.key,
name=symbol.fqsn,
shm=shm,
style=style,
_is_main=True,
sidepane=sidepane,
)
# add crosshair graphic
@ -615,12 +601,13 @@ class LinkedSplits(QWidget):
# TODO: we gotta possibly assign this back
# to the last subplot on removal of some last subplot
xaxis = DynamicDateAxis(
None,
orientation='bottom',
linkedsplits=self
)
axes = {
'right': PriceAxis(linkedsplits=self, orientation='right'),
'left': PriceAxis(linkedsplits=self, orientation='left'),
'right': PriceAxis(None, orientation='right'),
'left': PriceAxis(None, orientation='left'),
'bottom': xaxis,
}
@ -645,6 +632,11 @@ class LinkedSplits(QWidget):
axisItems=axes,
**cpw_kwargs,
)
# TODO: wow i can't believe how confusing garbage all this axes
# stuff iss..
for axis in axes.values():
axis.pi = cpw.plotItem
cpw.hideAxis('left')
cpw.hideAxis('bottom')
@ -860,7 +852,12 @@ class ChartPlotWidget(pg.PlotWidget):
# source of our custom interactions
self.cv = cv = self.mk_vb(name)
pi = pgo.PlotItem(viewBox=cv, **kwargs)
pi = pgo.PlotItem(
viewBox=cv,
name=name,
**kwargs,
)
pi.chart_widget = self
super().__init__(
background=hcolor(view_color),
viewBox=cv,
@ -913,18 +910,20 @@ class ChartPlotWidget(pg.PlotWidget):
self._on_screen: bool = False
def resume_all_feeds(self):
try:
for feed in self._feeds.values():
for flume in feed.flumes.values():
self.linked.godwidget._root_n.start_soon(feed.resume)
except RuntimeError:
# TODO: cancel the qtractor runtime here?
raise
...
# try:
# for feed in self._feeds.values():
# for flume in feed.flumes.values():
# self.linked.godwidget._root_n.start_soon(flume.resume)
# except RuntimeError:
# # TODO: cancel the qtractor runtime here?
# raise
def pause_all_feeds(self):
for feed in self._feeds.values():
for flume in feed.flumes.values():
self.linked.godwidget._root_n.start_soon(feed.pause)
...
# for feed in self._feeds.values():
# for flume in feed.flumes.values():
# self.linked.godwidget._root_n.start_soon(flume.pause)
@property
def view(self) -> ChartView:
@ -1116,43 +1115,6 @@ class ChartPlotWidget(pg.PlotWidget):
padding=0,
)
def draw_ohlc(
self,
name: str,
shm: ShmArray,
array_key: Optional[str] = None,
) -> (pg.GraphicsObject, str):
'''
Draw OHLC datums to chart.
'''
graphics = BarItems(
self.linked,
self.plotItem,
pen_color=self.pen_color,
name=name,
)
# adds all bar/candle graphics objects for each data point in
# the np array buffer to be drawn on next render cycle
self.plotItem.addItem(graphics)
data_key = array_key or name
self._flows[data_key] = Flow(
name=name,
plot=self.plotItem,
_shm=shm,
is_ohlc=True,
graphics=graphics,
)
self._add_sticky(name, bg_color='davies')
return graphics, data_key
def overlay_plotitem(
self,
name: str,
@ -1172,8 +1134,8 @@ class ChartPlotWidget(pg.PlotWidget):
raise ValueError(f'``axis_side``` must be in {allowed_sides}')
yaxis = PriceAxis(
plotitem=None,
orientation=axis_side,
linkedsplits=self.linked,
**axis_kwargs,
)
@ -1188,6 +1150,8 @@ class ChartPlotWidget(pg.PlotWidget):
},
default_axes=[],
)
yaxis.pi = pi
pi.chart_widget = self
pi.hideButtons()
# compose this new plot's graphics with the current chart's
@ -1231,43 +1195,60 @@ class ChartPlotWidget(pg.PlotWidget):
add_label: bool = True,
pi: Optional[pg.PlotItem] = None,
step_mode: bool = False,
is_ohlc: bool = False,
add_sticky: None | str = 'right',
**pdi_kwargs,
**graphics_kwargs,
) -> (pg.PlotDataItem, str):
) -> tuple[
pg.GraphicsObject,
str,
]:
'''
Draw a "curve" (line plot graphics) for the provided data in
the input shm array ``shm``.
'''
color = color or self.pen_color or 'default_light'
pdi_kwargs.update({
'color': color
})
# graphics_kwargs.update({
# 'color': color
# })
data_key = array_key or name
curve_type = {
None: Curve,
'step': StepCurve,
# TODO:
# 'bars': BarsItems
}['step' if step_mode else None]
curve = curve_type(
name=name,
**pdi_kwargs,
)
pi = pi or self.plotItem
if is_ohlc:
graphics = BarItems(
linked=self.linked,
plotitem=pi,
# pen_color=self.pen_color,
color=color,
name=name,
**graphics_kwargs,
)
else:
curve_type = {
None: Curve,
'step': StepCurve,
# TODO:
# 'bars': BarsItems
}['step' if step_mode else None]
graphics = curve_type(
name=name,
color=color,
**graphics_kwargs,
)
self._flows[data_key] = Flow(
name=name,
plot=pi,
_shm=shm,
is_ohlc=False,
is_ohlc=is_ohlc,
# register curve graphics with this flow
graphics=curve,
graphics=graphics,
)
# TODO: this probably needs its own method?
@ -1278,12 +1259,41 @@ class ChartPlotWidget(pg.PlotWidget):
f'{overlay} must be from `.plotitem_overlay()`'
)
pi = overlay
else:
# anchor_at = ('top', 'left')
# TODO: something instead of stickies for overlays
# (we need something that avoids clutter on x-axis).
self._add_sticky(name, bg_color=color)
if add_sticky:
axis = pi.getAxis(add_sticky)
if pi.name not in axis._stickies:
if pi is not self.plotItem:
overlay = self.pi_overlay
assert pi in overlay.overlays
overlay_axis = overlay.get_axis(
pi,
add_sticky,
)
assert overlay_axis is axis
# TODO: UGH! just make this not here! we should
# be making the sticky from code which has access
# to the ``Symbol`` instance..
# if the sticky is for our symbol
# use the tick size precision for display
name = name or pi.name
sym = self.linked.symbol
digits = None
if name == sym.key:
digits = sym.tick_size_digits
# anchor_at = ('top', 'left')
# TODO: something instead of stickies for overlays
# (we need something that avoids clutter on x-axis).
axis.add_sticky(
pi=pi,
bg_color=color,
digits=digits,
)
# NOTE: this is more or less the RENDER call that tells Qt to
# start showing the generated graphics-curves. This is kind of
@ -1294,38 +1304,30 @@ class ChartPlotWidget(pg.PlotWidget):
# the next render cycle; just note a lot of the real-time
# updates are implicit and require a bit of digging to
# understand.
pi.addItem(curve)
pi.addItem(graphics)
return curve, data_key
return graphics, data_key
# TODO: make this a ctx mngr
def _add_sticky(
def draw_ohlc(
self,
name: str,
bg_color='bracket',
shm: ShmArray,
) -> YAxisLabel:
array_key: Optional[str] = None,
**draw_curve_kwargs,
# if the sticky is for our symbol
# use the tick size precision for display
sym = self.linked.symbol
if name == sym.key:
digits = sym.tick_size_digits
else:
digits = 2
) -> (pg.GraphicsObject, str):
'''
Draw OHLC datums to chart.
# add y-axis "last" value label
last = self._ysticks[name] = YAxisLabel(
chart=self,
# parent=self.getAxis('right'),
parent=self.pi_overlay.get_axis(self.plotItem, 'right'),
# TODO: pass this from symbol data
digits=digits,
opacity=1,
bg_color=bg_color,
'''
return self.draw_curve(
name=name,
shm=shm,
array_key=array_key,
is_ohlc=True,
**draw_curve_kwargs,
)
return last
def update_graphics_from_flow(
self,