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
parent
f0d417ce42
commit
9846396df2
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue