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()`.multichartz_backup
parent
2bb2413391
commit
ec48d2fbeb
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue