Add initial history (view) to charting sys

Adds an additional `GodWidget.hist_linked: LinkedSplits` alongside the
renamed `.rt_linked` to enable 2 sets of linked charts with different
sampled data sets/flows. The history set is added without "all the
fixins" for now (i.e. no order mode sidepane or search integration) such
that it is merely a top level chart which shows a much longer term
history and can be added to the UI via embedding the entire history
linked-splits instance into the real-time linked set's splitter.

Further impl deats:
- adjust the `GodWidget._chart_cache: dict[str, tuple]]` to store both
  linked split chart sets per symbol so that symbol switching will
  continue to work with the added history chart (set).
- rework `.load_symbol()` to operate on both the real-time (HFT) chart
  set and the history set.
- rework `LinkedSplits.set_split_sizes()` to compensate for the history
  chart and do more detailed height calcs arithmetic to make it appear
  by default as a minor sub-chart.
- adjust `LinkedSplits.add_plot()` and `ChartPlotWidget` internals to allow
  adding a plot without a sidepane and/or container `ChartnPane`
  composite widget by checking for a `sidepane == False` input.
- make `.default_view()` accept a manual y-axis offset kwarg.
- adjust search mode to provide history linked splits to
  `.set_chart_symbol()` call.
history_view
Tyler Goodlet 2022-08-30 19:09:18 -04:00
parent f0d417ce42
commit 9846396df2
2 changed files with 112 additions and 67 deletions

View File

@ -115,7 +115,9 @@ class GodWidget(QWidget):
# self.vbox.addLayout(self.hbox) # self.vbox.addLayout(self.hbox)
self._chart_cache: dict[str, LinkedSplits] = {} self._chart_cache: dict[str, LinkedSplits] = {}
self.linkedsplits: Optional[LinkedSplits] = None
self.hist_linked: Optional[LinkedSplits] = None
self.rt_linked: Optional[LinkedSplits] = None
# assigned in the startup func `_async_main()` # assigned in the startup func `_async_main()`
self._root_n: trio.Nursery = None self._root_n: trio.Nursery = None
@ -123,6 +125,10 @@ class GodWidget(QWidget):
self._widgets: dict[str, QWidget] = {} self._widgets: dict[str, QWidget] = {}
self._resizing: bool = False self._resizing: bool = False
@property
def linkedsplits(self) -> LinkedSplits:
return self.rt_linked
# def init_timeframes_ui(self): # def init_timeframes_ui(self):
# self.tf_layout = QHBoxLayout() # self.tf_layout = QHBoxLayout()
# self.tf_layout.setSpacing(0) # self.tf_layout.setSpacing(0)
@ -148,19 +154,19 @@ class GodWidget(QWidget):
def set_chart_symbol( def set_chart_symbol(
self, self,
symbol_key: str, # of form <fqsn>.<providername> symbol_key: str, # of form <fqsn>.<providername>
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(symbol_key, None)
cache[symbol_key] = linkedsplits cache[symbol_key] = all_linked
def get_chart_symbol( def get_chart_symbol(
self, self,
symbol_key: str, symbol_key: str,
) -> LinkedSplits: # type: ignore ) -> tuple[LinkedSplits, LinkedSplits]: # type: ignore
return self._chart_cache.get(symbol_key) return self._chart_cache.get(symbol_key)
async def load_symbol( async def load_symbol(
@ -182,28 +188,30 @@ class GodWidget(QWidget):
# fully qualified symbol name (SNS i guess is what we're making?) # fully qualified symbol name (SNS i guess is what we're making?)
fqsn = '.'.join([symbol_key, providername]) fqsn = '.'.join([symbol_key, providername])
all_linked = self.get_chart_symbol(fqsn)
linkedsplits = self.get_chart_symbol(fqsn)
order_mode_started = trio.Event() order_mode_started = trio.Event()
if not self.vbox.isEmpty(): if not self.vbox.isEmpty():
for linked in [self.rt_linked, self.hist_linked]:
# XXX: this is CRITICAL especially with pixel buffer caching # XXX: this is CRITICAL especially with pixel buffer caching
self.linkedsplits.hide() linked.hide()
self.linkedsplits.unfocus() linked.unfocus()
# self.hist_linked.hide()
# self.hist_linked.unfocus()
# XXX: pretty sure we don't need this # XXX: pretty sure we don't need this
# remove any existing plots? # remove any existing plots?
# XXX: ahh we might want to support cache unloading.. # XXX: ahh we might want to support cache unloading..
# self.vbox.removeWidget(self.linkedsplits) # self.vbox.removeWidget(linked)
# switching to a new viewable chart # switching to a new viewable chart
if linkedsplits is None or reset: if all_linked is None or reset:
from ._display import display_symbol_data from ._display import display_symbol_data
# we must load a fresh linked charts set # we must load a fresh linked charts set
linkedsplits = LinkedSplits(self) self.rt_linked = rt_charts = LinkedSplits(self)
self.hist_linked = hist_charts = LinkedSplits(self)
# spawn new task to start up and update new sub-chart instances # spawn new task to start up and update new sub-chart instances
self._root_n.start_soon( self._root_n.start_soon(
@ -215,44 +223,53 @@ class GodWidget(QWidget):
order_mode_started, order_mode_started,
) )
self.set_chart_symbol(fqsn, linkedsplits) # self.vbox.addWidget(hist_charts)
self.vbox.addWidget(linkedsplits) self.vbox.addWidget(rt_charts)
self.set_chart_symbol(
fqsn,
(hist_charts, rt_charts),
)
for linked in [hist_charts, rt_charts]:
linked.show()
linked.focus()
linkedsplits.show()
linkedsplits.focus()
await trio.sleep(0) await trio.sleep(0)
else: else:
# symbol is already loaded and ems ready # symbol is already loaded and ems ready
order_mode_started.set() order_mode_started.set()
# TODO: self.hist_linked, self.rt_linked = all_linked
# - we'll probably want per-instrument/provider state here?
# change the order config form over to the new chart
# chart is already in memory so just focus it for linked in all_linked:
linkedsplits.show() # TODO:
linkedsplits.focus() # - we'll probably want per-instrument/provider state here?
linkedsplits.graphics_cycle() # change the order config form over to the new chart
await trio.sleep(0)
# XXX: since the pp config is a singleton widget we have to # chart is already in memory so just focus it
# also switch it over to the new chart's interal-layout linked.show()
# self.linkedsplits.chart.qframe.hbox.removeWidget(self.pp_pane) linked.focus()
chart = linkedsplits.chart linked.graphics_cycle()
await trio.sleep(0)
# resume feeds *after* rendering chart view asap # XXX: since the pp config is a singleton widget we have to
if chart: # also switch it over to the new chart's interal-layout
chart.resume_all_feeds() # linked.chart.qframe.hbox.removeWidget(self.pp_pane)
chart = linked.chart
# TODO: we need a check to see if the chart # resume feeds *after* rendering chart view asap
# last had the xlast in view, if so then shift so it's if chart:
# still in view, if the user was viewing history then chart.resume_all_feeds()
# do nothing yah?
chart.default_view()
self.linkedsplits = linkedsplits # TODO: we need a check to see if the chart
symbol = linkedsplits.symbol # last had the xlast in view, if so then shift so it's
# still in view, if the user was viewing history then
# do nothing yah?
chart.default_view()
# set window titlebar info
symbol = self.rt_linked.symbol
if symbol is not None: if symbol is not None:
self.window.setWindowTitle( self.window.setWindowTitle(
f'{symbol.front_fqsn()} ' f'{symbol.front_fqsn()} '
@ -399,10 +416,18 @@ class LinkedSplits(QWidget):
# elif ln >= 2: # elif ln >= 2:
# prop = 3/8 # prop = 3/8
major = 1 - prop h = self.height()
min_h_ind = int((self.height() * prop) / ln) histview_h = h * 1.6/6
h = h - histview_h
sizes = [int(self.height() * major)] major = 1 - prop
min_h_ind = int((h * prop) / ln)
sizes = [
int(histview_h),
int(h * major),
]
# give all subcharts the same remaining proportional height
sizes.extend([min_h_ind] * ln) sizes.extend([min_h_ind] * ln)
self.splitter.setSizes(sizes) self.splitter.setSizes(sizes)
@ -498,10 +523,15 @@ class LinkedSplits(QWidget):
'bottom': xaxis, 'bottom': xaxis,
} }
qframe = ChartnPane( if sidepane is not False:
sidepane=sidepane, parent = qframe = ChartnPane(
parent=self.splitter, sidepane=sidepane,
) parent=self.splitter,
)
else:
parent = self.splitter
qframe = None
cpw = ChartPlotWidget( cpw = ChartPlotWidget(
# this name will be used to register the primary # this name will be used to register the primary
@ -509,7 +539,7 @@ class LinkedSplits(QWidget):
name=name, name=name,
data_key=array_key or name, data_key=array_key or name,
parent=qframe, parent=parent,
linkedsplits=self, linkedsplits=self,
axisItems=axes, axisItems=axes,
**cpw_kwargs, **cpw_kwargs,
@ -537,20 +567,22 @@ class LinkedSplits(QWidget):
self.xaxis_chart = cpw self.xaxis_chart = cpw
cpw.showAxis('bottom') cpw.showAxis('bottom')
qframe.chart = cpw if qframe is not None:
qframe.hbox.addWidget(cpw) qframe.chart = cpw
qframe.hbox.addWidget(cpw)
# so we can look this up and add back to the splitter # so we can look this up and add back to the splitter
# on a symbol switch # on a symbol switch
cpw.qframe = qframe cpw.qframe = qframe
assert cpw.parent() == qframe assert cpw.parent() == qframe
# add sidepane **after** chart; place it on axis side # add sidepane **after** chart; place it on axis side
qframe.hbox.addWidget( qframe.hbox.addWidget(
sidepane, sidepane,
alignment=Qt.AlignTop alignment=Qt.AlignTop
) )
cpw.sidepane = sidepane
cpw.sidepane = sidepane
cpw.plotItem.vb.linkedsplits = self cpw.plotItem.vb.linkedsplits = self
cpw.setFrameStyle( cpw.setFrameStyle(
@ -613,7 +645,9 @@ class LinkedSplits(QWidget):
if not _is_main: if not _is_main:
# track by name # track by name
self.subplots[name] = cpw self.subplots[name] = cpw
self.splitter.addWidget(qframe) if qframe is not None:
self.splitter.addWidget(qframe)
# scale split regions # scale split regions
self.set_split_sizes() self.set_split_sizes()
@ -648,7 +682,7 @@ class LinkedSplits(QWidget):
''' '''
main_chart = self.chart main_chart = self.chart
if main_chart: if main_chart and main_chart.sidepane:
sp_w = main_chart.sidepane.width() sp_w = main_chart.sidepane.width()
for name, cpw in self.subplots.items(): for name, cpw in self.subplots.items():
cpw.sidepane.setMinimumWidth(sp_w) cpw.sidepane.setMinimumWidth(sp_w)
@ -711,6 +745,7 @@ class ChartPlotWidget(pg.PlotWidget):
# NOTE: must be set bfore calling ``.mk_vb()`` # NOTE: must be set bfore calling ``.mk_vb()``
self.linked = linkedsplits self.linked = linkedsplits
self.sidepane: Optional[FieldsForm] = None
# source of our custom interactions # source of our custom interactions
self.cv = cv = self.mk_vb(name) self.cv = cv = self.mk_vb(name)
@ -867,7 +902,8 @@ class ChartPlotWidget(pg.PlotWidget):
def default_view( def default_view(
self, self,
bars_from_y: int = 616, bars_from_y: int = int(616 * 3/8),
y_offset: int = 0,
do_ds: bool = True, do_ds: bool = True,
) -> None: ) -> None:
@ -906,8 +942,12 @@ class ChartPlotWidget(pg.PlotWidget):
# terms now that we've scaled either by user control # terms now that we've scaled either by user control
# or to the default set of bars as per the immediate block # or to the default set of bars as per the immediate block
# above. # above.
marker_pos, l1_len = self.pre_l1_xs() if not y_offset:
end = xlast + l1_len + 1 marker_pos, l1_len = self.pre_l1_xs()
end = xlast + l1_len + 1
else:
end = xlast + y_offset + 1
begin = end - (r - l) begin = end - (r - l)
# for debugging # for debugging

View File

@ -635,7 +635,12 @@ class SearchWidget(QtWidgets.QWidget):
# Re-order the symbol cache on the chart to display in # Re-order the symbol cache on the chart to display in
# LIFO order. this is normally only done internally by # LIFO order. this is normally only done internally by
# the chart on new symbols being loaded into memory # the chart on new symbols being loaded into memory
chart.set_chart_symbol(fqsn, chart.linkedsplits) chart.set_chart_symbol(
fqsn, (
chart.linkedsplits,
self.godwidget.hist_linked,
)
)
self.view.set_section_entries( self.view.set_section_entries(
'cache', 'cache',