Add global `i_step` per overlay to `DisplayState`

Using a global "last index step" (via module var) obviously has problems
when working with multiple feed sets in a single global app instance:
any separate feed-set will be incremented according to an app-global
index-step and thus won't correctly calc per-feed-set-step update info.

Impl deatz:
- drop `DisplayState.incr_info()` (since previously moved to `Viz`) and
  call that method on each appropriate `Viz` instance where necessary;
  further ensure the appropriate `DisplayState` instance is passed in to
  each call and make sure to pass a `state: DisplayState`.
- add `DisplayState.hist_vars: dict` for history chart (sets) to
  determine the per-feed (not set) current slow chart (time) step.
- add `DisplayState.globalz: dict` to house a common per-feed-set state
  and use it inside the new `Viz.incr_info()` such that
  a `should_increment: bool` can be returned and used by the display
  loop to determine whether to x-shift the current chart.
multichartz
Tyler Goodlet 2022-12-14 17:36:01 -05:00
parent 1a3e2c33d9
commit eb0216feaf
1 changed files with 65 additions and 158 deletions

View File

@ -126,10 +126,6 @@ def chart_maxmin(
) )
_i_last: int = 0
_i_last_append: int = 0
class DisplayState(Struct): class DisplayState(Struct):
''' '''
Chart-local real-time graphics state container. Chart-local real-time graphics state container.
@ -160,105 +156,21 @@ class DisplayState(Struct):
'last_mx': 0, 'last_mx': 0,
'last_mn': 0, 'last_mn': 0,
} }
hist_vars: dict[str, Any] = {
'tick_margin': 0,
'i_last': 0,
'i_last_append': 0,
'last_mx_vlm': 0,
'last_mx': 0,
'last_mn': 0,
}
globalz: None | dict[str, Any] = None
vlm_chart: Optional[ChartPlotWidget] = None vlm_chart: Optional[ChartPlotWidget] = None
vlm_sticky: Optional[YAxisLabel] = None vlm_sticky: Optional[YAxisLabel] = None
wap_in_history: bool = False wap_in_history: bool = False
def incr_info(
self,
chart: Optional[ChartPlotWidget] = None,
shm: Optional[ShmArray] = None,
state: Optional[dict] = None, # pass in a copy if you don't
update_state: bool = True,
update_uppx: float = 16,
is_1m: bool = False,
) -> tuple:
shm = shm or self.ohlcv
chart = chart or self.chart
# state = state or self.vars
if (
not update_state
and state
):
state = state.copy()
# compute the first available graphic's x-units-per-pixel
uppx = chart.view.x_uppx()
# NOTE: this used to be implemented in a dedicated
# "increment task": ``check_for_new_bars()`` but it doesn't
# make sense to do a whole task switch when we can just do
# this simple index-diff and all the fsp sub-curve graphics
# are diffed on each draw cycle anyway; so updates to the
# "curve" length is already automatic.
# increment the view position by the sample offset.
# i_step = shm.index
i_step = shm.array[-1]['time']
# i_diff = i_step - state['i_last']
# state['i_last'] = i_step
global _i_last, _i_last_append
i_diff = i_step - _i_last
# update global state
if (
# state is None
not is_1m
and i_diff > 0
):
_i_last = i_step
# append_diff = i_step - state['i_last_append']
append_diff = i_step - _i_last_append
# real-time update necessary?
main_viz = chart.get_viz(chart.name)
_, _, _, r = main_viz.bars_range()
liv = r >= shm.index
# update the "last datum" (aka extending the vizs graphic with
# new data) only if the number of unit steps is >= the number of
# such unit steps per pixel (aka uppx). Iow, if the zoom level
# is such that a datum(s) update to graphics wouldn't span
# to a new pixel, we don't update yet.
do_append = (
append_diff >= uppx
and i_diff
)
if (
do_append
and not is_1m
):
_i_last_append = i_step
# fqsn = self.flume.symbol.fqsn
# print(
# f'DOING APPEND => {fqsn}\n'
# f'i_step:{i_step}\n'
# f'i_diff:{i_diff}\n'
# f'last:{_i_last}\n'
# f'last_append:{_i_last_append}\n'
# f'append_diff:{append_diff}\n'
# f'r: {r}\n'
# f'liv: {liv}\n'
# f'uppx: {uppx}\n'
# )
do_rt_update = uppx < update_uppx
# TODO: pack this into a struct
return (
uppx,
liv,
do_append,
i_diff,
append_diff,
do_rt_update,
)
async def graphics_update_loop( async def graphics_update_loop(
@ -293,7 +205,15 @@ async def graphics_update_loop(
hist_chart = godwidget.hist_linked.chart hist_chart = godwidget.hist_linked.chart
assert hist_chart assert hist_chart
# per-viz-set global last index tracking for global chart
# view UX incrementing.
globalz = {
'i_last': 0,
'i_last_append': 0,
}
dss: dict[str, DisplayState] = {} dss: dict[str, DisplayState] = {}
for fqsn, flume in feed.flumes.items(): for fqsn, flume in feed.flumes.items():
ohlcv = flume.rt_shm ohlcv = flume.rt_shm
hist_ohlcv = flume.hist_shm hist_ohlcv = flume.hist_shm
@ -313,7 +233,8 @@ async def graphics_update_loop(
) )
last_price_sticky.show() last_price_sticky.show()
slow_pi = hist_chart._vizs[fqsn].plot hist_viz = hist_chart._vizs[fqsn]
slow_pi = hist_viz.plot
hist_last_price_sticky = slow_pi.getAxis('right')._stickies[fqsn] hist_last_price_sticky = slow_pi.getAxis('right')._stickies[fqsn]
hist_last_price_sticky.update_from_data( hist_last_price_sticky.update_from_data(
*hist_ohlcv.array[-1][[ *hist_ohlcv.array[-1][[
@ -387,7 +308,8 @@ async def graphics_update_loop(
'last_mx_vlm': last_mx_vlm, 'last_mx_vlm': last_mx_vlm,
'last_mx': last_mx, 'last_mx': last_mx,
'last_mn': last_mn, 'last_mn': last_mn,
} },
'globalz': globalz,
}) })
if vlm_chart: if vlm_chart:
@ -398,15 +320,15 @@ async def graphics_update_loop(
fast_chart.default_view() fast_chart.default_view()
ds.hist_vars.update({
'i_last_append': i_last,
'i_last': i_last,
})
# TODO: probably factor this into some kinda `DisplayState` # TODO: probably factor this into some kinda `DisplayState`
# API that can be reused at least in terms of pulling view # API that can be reused at least in terms of pulling view
# params (eg ``.bars_range()``). # params (eg ``.bars_range()``).
async def increment_history_view(): async def increment_history_view():
i_last = hist_ohlcv.index
state = ds.vars.copy() | {
'i_last_append': i_last,
'i_last': i_last,
}
_, hist_step_size_s, _ = flume.get_ds_info() _, hist_step_size_s, _ = flume.get_ds_info()
async with flume.index_stream( async with flume.index_stream(
@ -431,12 +353,11 @@ async def graphics_update_loop(
i_diff, i_diff,
append_diff, append_diff,
do_rt_update, do_rt_update,
) = ds.incr_info( should_incr,
chart=hist_chart,
shm=ds.hist_ohlcv, ) = hist_viz.incr_info(
state=state, state=ds,
is_1m=True, is_1m=True,
# update_state=False,
) )
# print( # print(
# f'liv: {liv}\n' # f'liv: {liv}\n'
@ -557,40 +478,8 @@ def graphics_update_cycle(
i_diff, i_diff,
append_diff, append_diff,
do_rt_update, do_rt_update,
) = ds.incr_info() should_incr,
) = main_viz.incr_info(state=ds)
# don't real-time "shift" the curve to the
# left unless we get one of the following:
if (
(
do_append
and liv
)
or trigger_all
):
# print(f'INCREMENTING {fqsn}')
chart.increment_view(steps=i_diff)
main_viz.plot.vb._set_yrange(
# yrange=(mn, mx),
)
# NOTE: since vlm and ohlc charts are axis linked now we don't
# need the double increment request?
# if vlm_chart:
# vlm_chart.increment_view(steps=i_diff)
profiler('view incremented')
# frames_by_type: dict[str, dict] = {}
# lasts = {}
# build tick-type "frames" of tick sequences since
# likely the tick arrival rate is higher then our
# (throttled) quote stream rate.
# iterate in FIFO order per tick-frame
# if sym != fqsn:
# continue
# TODO: we should only run mxmn when we know # TODO: we should only run mxmn when we know
# an update is due via ``do_append`` above. # an update is due via ``do_append`` above.
@ -621,11 +510,6 @@ def graphics_update_cycle(
# graphic. # graphic.
clear_types = _tick_groups['clears'] clear_types = _tick_groups['clears']
# XXX: if we wanted to iterate in "latest" (i.e. most
# current) tick first order as an optimization where we only
# update from the last tick from each type class.
# last_clear_updated: bool = False
# update ohlc sampled price bars # update ohlc sampled price bars
if ( if (
do_rt_update do_rt_update
@ -645,6 +529,29 @@ def graphics_update_cycle(
# do_append=do_append, # do_append=do_append,
) )
# don't real-time "shift" the curve to the
# left unless we get one of the following:
if (
(
should_incr
and do_append
and liv
)
or trigger_all
):
# print(f'INCREMENTING {fqsn}')
chart.increment_view(steps=i_diff)
main_viz.plot.vb._set_yrange(
# yrange=(mn, mx),
)
# NOTE: since vlm and ohlc charts are axis linked now we don't
# need the double increment request?
# if vlm_chart:
# vlm_chart.increment_view(steps=i_diff)
profiler('view incremented')
# from pprint import pformat # from pprint import pformat
# frame_counts = { # frame_counts = {
# typ: len(frame) for typ, frame in frames_by_type.items() # typ: len(frame) for typ, frame in frames_by_type.items()
@ -765,6 +672,8 @@ def graphics_update_cycle(
) )
# check if slow chart needs a resize # check if slow chart needs a resize
hist_viz = hist_chart._vizs[fqsn]
( (
_, _,
hist_liv, hist_liv,
@ -772,15 +681,13 @@ def graphics_update_cycle(
_, _,
_, _,
_, _,
) = ds.incr_info( _,
chart=hist_chart, ) = hist_viz.incr_info(
shm=ds.hist_ohlcv, state=ds,
update_state=False,
is_1m=True, is_1m=True,
) )
if hist_liv: if hist_liv:
viz = hist_chart._vizs[fqsn] hist_viz.plot.vb._set_yrange(
viz.plot.vb._set_yrange(
# yrange=hist_chart.maxmin(name=fqsn), # yrange=hist_chart.maxmin(name=fqsn),
) )
@ -814,7 +721,7 @@ def graphics_update_cycle(
): ):
viz.draw_last( viz.draw_last(
array_key=curve_name, array_key=curve_name,
only_last_uppx=True, # only_last_uppx=True,
) )
# volume chart logic.. # volume chart logic..
@ -1207,7 +1114,7 @@ async def display_symbol_data(
# for zoom-interaction purposes. # for zoom-interaction purposes.
hist_chart.get_viz(fqsn).draw_last( hist_chart.get_viz(fqsn).draw_last(
array_key=fqsn, array_key=fqsn,
only_last_uppx=True, # only_last_uppx=True,
) )
pis.setdefault(fqsn, [None, None])[1] = hist_chart.plotItem pis.setdefault(fqsn, [None, None])[1] = hist_chart.plotItem
@ -1306,7 +1213,7 @@ async def display_symbol_data(
# for zoom-interaction purposes. # for zoom-interaction purposes.
viz.draw_last( viz.draw_last(
array_key=fqsn, array_key=fqsn,
only_last_uppx=True, # only_last_uppx=True,
) )
hist_pi.vb.maxmin = partial( hist_pi.vb.maxmin = partial(