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