Rework display loop maxmin-ing with `Viz` pipelining
First, we rename what was `chart_maxmin()` -> `multi_maxmin()` and don't `partial` it in to the `DisplayState`, just call it with correct `Viz` ref inputs. Second, as we've done with `ChartView.maybe_downsample_graphics()` use the output from the main `Viz.update_graphics()` and feed it to the `.maxmin()` calls for the ohlc and vlm chart but still deliver the same output signature as prior. Also accept and use an optional profiler input, drop `DisplayState.maxmin()` and add `.vlm_viz`. Further perf related tweak to do with more efficient incremental updates: - only call `multi_maxmin()` if the main fast chart viz does a pixel column step. - mask out hist viz and vlm viz and all linked fsp `._set_yrange()` calls for now until we figure out how to best optimize these updates when considering the new group-scaled-by-% style for multicharts. - drop `.enable_auto_yrange()` calls during startup.multichartz
parent
e44b485bcb
commit
47ffe60047
|
@ -25,7 +25,10 @@ from functools import partial
|
||||||
import itertools
|
import itertools
|
||||||
from math import floor
|
from math import floor
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Any, Callable
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
Any,
|
||||||
|
)
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
import trio
|
import trio
|
||||||
|
@ -85,10 +88,11 @@ log = get_logger(__name__)
|
||||||
# approach, likely with ``numba``:
|
# approach, likely with ``numba``:
|
||||||
# https://arxiv.org/abs/cs/0610046
|
# https://arxiv.org/abs/cs/0610046
|
||||||
# https://github.com/lemire/pythonmaxmin
|
# https://github.com/lemire/pythonmaxmin
|
||||||
def chart_maxmin(
|
def multi_maxmin(
|
||||||
|
i_read_range: tuple[int, int] | None,
|
||||||
fast_viz: Viz,
|
fast_viz: Viz,
|
||||||
fqsn: str,
|
|
||||||
vlm_viz: Viz | None = None,
|
vlm_viz: Viz | None = None,
|
||||||
|
profiler: Profiler = None,
|
||||||
|
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
|
|
||||||
|
@ -102,26 +106,32 @@ def chart_maxmin(
|
||||||
Compute max and min datums "in view" for range limits.
|
Compute max and min datums "in view" for range limits.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
out = fast_viz.maxmin()
|
out = fast_viz.maxmin(
|
||||||
|
i_read_range=i_read_range,
|
||||||
|
)
|
||||||
if out is None:
|
if out is None:
|
||||||
|
# log.warning(f'No yrange provided for {name}!?')
|
||||||
return (0, 0, 0)
|
return (0, 0, 0)
|
||||||
|
|
||||||
(
|
(
|
||||||
ixrng,
|
ixrng,
|
||||||
read_slc,
|
read_slc,
|
||||||
mxmn,
|
yrange,
|
||||||
) = out
|
) = out
|
||||||
|
|
||||||
mn, mx = mxmn
|
if profiler:
|
||||||
|
profiler('fast_viz.maxmin({read_slice})')
|
||||||
|
|
||||||
mx_vlm_in_view = 0
|
mn, mx = yrange
|
||||||
|
|
||||||
# TODO: we need to NOT call this to avoid a manual
|
# TODO: we need to NOT call this to avoid a manual
|
||||||
# np.max/min trigger and especially on the vlm_chart
|
# np.max/min trigger and especially on the vlm_chart
|
||||||
# vizs which aren't shown.. like vlm?
|
# vizs which aren't shown.. like vlm?
|
||||||
|
mx_vlm_in_view = 0
|
||||||
if vlm_viz:
|
if vlm_viz:
|
||||||
out = vlm_viz.maxmin()
|
out = vlm_viz.maxmin(
|
||||||
|
i_read_range=i_read_range,
|
||||||
|
)
|
||||||
if out:
|
if out:
|
||||||
(
|
(
|
||||||
ixrng,
|
ixrng,
|
||||||
|
@ -130,9 +140,16 @@ def chart_maxmin(
|
||||||
) = out
|
) = out
|
||||||
mx_vlm_in_view = mxmn[1]
|
mx_vlm_in_view = mxmn[1]
|
||||||
|
|
||||||
|
if profiler:
|
||||||
|
profiler('vlm_viz.maxmin({read_slc})')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
mx,
|
mx,
|
||||||
max(mn, 0), # presuming price can't be negative?
|
|
||||||
|
# enforcing price can't be negative?
|
||||||
|
# TODO: do we even need this?
|
||||||
|
max(mn, 0),
|
||||||
|
|
||||||
mx_vlm_in_view, # vlm max
|
mx_vlm_in_view, # vlm max
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -146,7 +163,6 @@ class DisplayState(Struct):
|
||||||
godwidget: GodWidget
|
godwidget: GodWidget
|
||||||
quotes: dict[str, Any]
|
quotes: dict[str, Any]
|
||||||
|
|
||||||
maxmin: Callable
|
|
||||||
flume: Flume
|
flume: Flume
|
||||||
|
|
||||||
# high level chart handles and underlying ``Viz``
|
# high level chart handles and underlying ``Viz``
|
||||||
|
@ -160,6 +176,8 @@ class DisplayState(Struct):
|
||||||
last_price_sticky: YAxisLabel
|
last_price_sticky: YAxisLabel
|
||||||
hist_last_price_sticky: YAxisLabel
|
hist_last_price_sticky: YAxisLabel
|
||||||
|
|
||||||
|
vlm_viz: Viz
|
||||||
|
|
||||||
# misc state tracking
|
# misc state tracking
|
||||||
vars: dict[str, Any] = {
|
vars: dict[str, Any] = {
|
||||||
'tick_margin': 0,
|
'tick_margin': 0,
|
||||||
|
@ -232,21 +250,22 @@ async def increment_history_view(
|
||||||
is_1m=True,
|
is_1m=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if do_px_step:
|
||||||
|
hist_viz.update_graphics()
|
||||||
|
profiler('`hist Viz.update_graphics()` call')
|
||||||
|
|
||||||
|
if liv:
|
||||||
|
hist_viz.plot.vb._set_yrange(viz=hist_viz)
|
||||||
|
profiler('hist chart yrange view')
|
||||||
|
|
||||||
# check if tread-in-place view x-shift is needed
|
# check if tread-in-place view x-shift is needed
|
||||||
if should_tread:
|
if should_tread:
|
||||||
# ensure path graphics append is shown on treads since
|
# ensure path graphics append is shown on treads since
|
||||||
# the main rt loop does not call this.
|
# the main rt loop does not call this.
|
||||||
hist_viz.update_graphics()
|
|
||||||
profiler('`hist Viz.update_graphics()` call')
|
|
||||||
|
|
||||||
hist_chart.increment_view(datums=append_diff)
|
hist_chart.increment_view(datums=append_diff)
|
||||||
profiler('hist tread view')
|
profiler('hist tread view')
|
||||||
|
|
||||||
if (
|
profiler.finish()
|
||||||
do_px_step
|
|
||||||
and liv
|
|
||||||
):
|
|
||||||
hist_viz.plot.vb._set_yrange(viz=hist_viz)
|
|
||||||
|
|
||||||
|
|
||||||
async def graphics_update_loop(
|
async def graphics_update_loop(
|
||||||
|
@ -325,17 +344,15 @@ async def graphics_update_loop(
|
||||||
vlm_chart = vlm_charts[fqsn]
|
vlm_chart = vlm_charts[fqsn]
|
||||||
vlm_viz = vlm_chart._vizs.get('volume') if vlm_chart else None
|
vlm_viz = vlm_chart._vizs.get('volume') if vlm_chart else None
|
||||||
|
|
||||||
maxmin = partial(
|
|
||||||
chart_maxmin,
|
|
||||||
fast_viz,
|
|
||||||
fqsn,
|
|
||||||
vlm_viz,
|
|
||||||
)
|
|
||||||
(
|
(
|
||||||
last_mx,
|
last_mx,
|
||||||
last_mn,
|
last_mn,
|
||||||
last_mx_vlm,
|
last_mx_vlm,
|
||||||
) = maxmin()
|
) = multi_maxmin(
|
||||||
|
None,
|
||||||
|
fast_viz,
|
||||||
|
vlm_viz,
|
||||||
|
)
|
||||||
|
|
||||||
last, volume = ohlcv.array[-1][['close', 'volume']]
|
last, volume = ohlcv.array[-1][['close', 'volume']]
|
||||||
|
|
||||||
|
@ -366,7 +383,7 @@ async def graphics_update_loop(
|
||||||
'fqsn': fqsn,
|
'fqsn': fqsn,
|
||||||
'godwidget': godwidget,
|
'godwidget': godwidget,
|
||||||
'quotes': {},
|
'quotes': {},
|
||||||
'maxmin': maxmin,
|
# 'maxmin': maxmin,
|
||||||
|
|
||||||
'flume': flume,
|
'flume': flume,
|
||||||
|
|
||||||
|
@ -378,6 +395,8 @@ async def graphics_update_loop(
|
||||||
'hist_viz': hist_viz,
|
'hist_viz': hist_viz,
|
||||||
'hist_last_price_sticky': hist_last_price_sticky,
|
'hist_last_price_sticky': hist_last_price_sticky,
|
||||||
|
|
||||||
|
'vlm_viz': vlm_viz,
|
||||||
|
|
||||||
'l1': l1,
|
'l1': l1,
|
||||||
|
|
||||||
'vars': {
|
'vars': {
|
||||||
|
@ -485,11 +504,10 @@ def graphics_update_cycle(
|
||||||
# or at least a little `mypyc` B)
|
# or at least a little `mypyc` B)
|
||||||
# - pass more direct refs as input to avoid so many attr accesses?
|
# - pass more direct refs as input to avoid so many attr accesses?
|
||||||
# - use a streaming minmax algo and drop the use of the
|
# - use a streaming minmax algo and drop the use of the
|
||||||
# state-tracking ``chart_maxmin()`` routine from above?
|
# state-tracking ``multi_maxmin()`` routine from above?
|
||||||
|
|
||||||
fqsn = ds.fqsn
|
fqsn = ds.fqsn
|
||||||
chart = ds.chart
|
chart = ds.chart
|
||||||
hist_chart = ds.hist_chart
|
|
||||||
vlm_chart = ds.vlm_chart
|
vlm_chart = ds.vlm_chart
|
||||||
|
|
||||||
varz = ds.vars
|
varz = ds.vars
|
||||||
|
@ -513,25 +531,19 @@ def graphics_update_cycle(
|
||||||
do_rt_update,
|
do_rt_update,
|
||||||
should_tread,
|
should_tread,
|
||||||
) = main_viz.incr_info(ds=ds)
|
) = main_viz.incr_info(ds=ds)
|
||||||
|
|
||||||
profiler('`.incr_info()`')
|
profiler('`.incr_info()`')
|
||||||
|
|
||||||
# TODO: we should only run mxmn when we know
|
# TODO: we should only run mxmn when we know
|
||||||
# an update is due via ``do_px_step`` above.
|
# an update is due via ``do_px_step`` above.
|
||||||
(
|
|
||||||
mx_in_view,
|
|
||||||
mn_in_view,
|
|
||||||
mx_vlm_in_view,
|
|
||||||
) = ds.maxmin()
|
|
||||||
|
|
||||||
mx = mx_in_view + tick_margin
|
|
||||||
mn = mn_in_view - tick_margin
|
|
||||||
profiler('`ds.maxmin()` call')
|
|
||||||
|
|
||||||
# TODO: eventually we want to separate out the dark vlm and show
|
# TODO: eventually we want to separate out the dark vlm and show
|
||||||
# them as an additional graphic.
|
# them as an additional graphic.
|
||||||
clear_types = _tick_groups['clears']
|
clear_types = _tick_groups['clears']
|
||||||
|
|
||||||
|
mx = varz['last_mx']
|
||||||
|
mn = varz['last_mn']
|
||||||
|
mx_vlm_in_view = varz['last_mx_vlm']
|
||||||
|
|
||||||
# update ohlc sampled price bars
|
# update ohlc sampled price bars
|
||||||
if (
|
if (
|
||||||
# do_rt_update
|
# do_rt_update
|
||||||
|
@ -539,9 +551,24 @@ def graphics_update_cycle(
|
||||||
(liv and do_px_step)
|
(liv and do_px_step)
|
||||||
or trigger_all
|
or trigger_all
|
||||||
):
|
):
|
||||||
main_viz.update_graphics(array_key=fqsn)
|
i_read_range, _ = main_viz.update_graphics()
|
||||||
profiler('`Viz.update_graphics()` call')
|
profiler('`Viz.update_graphics()` call')
|
||||||
|
|
||||||
|
(
|
||||||
|
mx_in_view,
|
||||||
|
mn_in_view,
|
||||||
|
mx_vlm_in_view,
|
||||||
|
) = multi_maxmin(
|
||||||
|
i_read_range,
|
||||||
|
main_viz,
|
||||||
|
ds.vlm_viz,
|
||||||
|
profiler,
|
||||||
|
)
|
||||||
|
|
||||||
|
mx = mx_in_view + tick_margin
|
||||||
|
mn = mn_in_view - tick_margin
|
||||||
|
profiler('{fqsdn} `multi_maxmin()` call')
|
||||||
|
|
||||||
# don't real-time "shift" the curve to the
|
# don't real-time "shift" the curve to the
|
||||||
# left unless we get one of the following:
|
# left unless we get one of the following:
|
||||||
if (
|
if (
|
||||||
|
@ -711,15 +738,21 @@ def graphics_update_cycle(
|
||||||
is_1m=True,
|
is_1m=True,
|
||||||
)
|
)
|
||||||
profiler('hist `Viz.incr_info()`')
|
profiler('hist `Viz.incr_info()`')
|
||||||
if (
|
|
||||||
hist_liv
|
# TODO: track local liv maxmin without doing a recompute all the
|
||||||
and not hist_chart._static_yrange == 'axis'
|
# time..plut, just generally the user is more likely to be
|
||||||
):
|
# zoomed out enough on the slow chart that this is never an
|
||||||
hist_viz.plot.vb._set_yrange(
|
# issue (the last datum going out of y-range).
|
||||||
viz=hist_viz,
|
# hist_chart = ds.hist_chart
|
||||||
# yrange=yr, # this is the rt range, not hist.. XD
|
# if (
|
||||||
)
|
# hist_liv
|
||||||
profiler('hist vb y-autorange')
|
# and not hist_chart._static_yrange == 'axis'
|
||||||
|
# ):
|
||||||
|
# hist_viz.plot.vb._set_yrange(
|
||||||
|
# viz=hist_viz,
|
||||||
|
# # yrange=yr, # this is the rt range, not hist.. XD
|
||||||
|
# )
|
||||||
|
# profiler('hist vb y-autorange')
|
||||||
|
|
||||||
# XXX: update this every draw cycle to ensure y-axis auto-ranging
|
# XXX: update this every draw cycle to ensure y-axis auto-ranging
|
||||||
# only adjusts when the in-view data co-domain actually expands or
|
# only adjusts when the in-view data co-domain actually expands or
|
||||||
|
@ -810,10 +843,10 @@ def graphics_update_cycle(
|
||||||
if (
|
if (
|
||||||
mx_vlm_in_view != varz['last_mx_vlm']
|
mx_vlm_in_view != varz['last_mx_vlm']
|
||||||
):
|
):
|
||||||
vlm_yr = (0, mx_vlm_in_view * 1.375)
|
|
||||||
vlm_chart.view._set_yrange(yrange=vlm_yr)
|
|
||||||
profiler('`vlm_chart.view._set_yrange()`')
|
|
||||||
varz['last_mx_vlm'] = mx_vlm_in_view
|
varz['last_mx_vlm'] = mx_vlm_in_view
|
||||||
|
# vlm_yr = (0, mx_vlm_in_view * 1.375)
|
||||||
|
# vlm_chart.view._set_yrange(yrange=vlm_yr)
|
||||||
|
# profiler('`vlm_chart.view._set_yrange()`')
|
||||||
|
|
||||||
# update all downstream FSPs
|
# update all downstream FSPs
|
||||||
for curve_name, viz in vlm_vizs.items():
|
for curve_name, viz in vlm_vizs.items():
|
||||||
|
@ -839,13 +872,12 @@ def graphics_update_cycle(
|
||||||
# is this even doing anything?
|
# is this even doing anything?
|
||||||
# (pretty sure it's the real-time
|
# (pretty sure it's the real-time
|
||||||
# resizing from last quote?)
|
# resizing from last quote?)
|
||||||
fvb = viz.plot.vb
|
|
||||||
|
|
||||||
# XXX: without this we get completely
|
# XXX: without this we get completely
|
||||||
# mangled/empty vlm display subchart..
|
# mangled/empty vlm display subchart..
|
||||||
fvb._set_yrange(
|
# fvb = viz.plot.vb
|
||||||
viz=viz,
|
# fvb._set_yrange(
|
||||||
)
|
# viz=viz,
|
||||||
|
# )
|
||||||
profiler(f'vlm `Viz[{viz.name}].plot.vb._set_yrange()`')
|
profiler(f'vlm `Viz[{viz.name}].plot.vb._set_yrange()`')
|
||||||
|
|
||||||
# even if we're downsampled bigly
|
# even if we're downsampled bigly
|
||||||
|
@ -1189,9 +1221,8 @@ async def display_symbol_data(
|
||||||
|
|
||||||
# ensure the last datum graphic is generated
|
# ensure the last datum graphic is generated
|
||||||
# for zoom-interaction purposes.
|
# for zoom-interaction purposes.
|
||||||
hist_chart.get_viz(fqsn).draw_last(
|
hist_viz = hist_chart.get_viz(fqsn)
|
||||||
array_key=fqsn,
|
hist_viz.draw_last(array_key=fqsn)
|
||||||
)
|
|
||||||
pis.setdefault(fqsn, [None, None])[1] = hist_chart.plotItem
|
pis.setdefault(fqsn, [None, None])[1] = hist_chart.plotItem
|
||||||
|
|
||||||
# don't show when not focussed
|
# don't show when not focussed
|
||||||
|
@ -1205,6 +1236,7 @@ async def display_symbol_data(
|
||||||
# to avoid internal pane creation.
|
# to avoid internal pane creation.
|
||||||
sidepane=pp_pane,
|
sidepane=pp_pane,
|
||||||
)
|
)
|
||||||
|
rt_viz = rt_chart.get_viz(fqsn)
|
||||||
pis.setdefault(fqsn, [None, None])[0] = rt_chart.plotItem
|
pis.setdefault(fqsn, [None, None])[0] = rt_chart.plotItem
|
||||||
|
|
||||||
# for pause/resume on mouse interaction
|
# for pause/resume on mouse interaction
|
||||||
|
@ -1221,7 +1253,7 @@ async def display_symbol_data(
|
||||||
and has_vlm(ohlcv)
|
and has_vlm(ohlcv)
|
||||||
and vlm_chart is None
|
and vlm_chart is None
|
||||||
):
|
):
|
||||||
vlm_charts[fqsn] = await ln.start(
|
vlm_chart = vlm_charts[fqsn] = await ln.start(
|
||||||
open_vlm_displays,
|
open_vlm_displays,
|
||||||
rt_linked,
|
rt_linked,
|
||||||
flume,
|
flume,
|
||||||
|
@ -1277,7 +1309,7 @@ async def display_symbol_data(
|
||||||
# are none?
|
# are none?
|
||||||
hist_pi.hideAxis('left')
|
hist_pi.hideAxis('left')
|
||||||
|
|
||||||
viz = hist_chart.draw_curve(
|
hist_viz = hist_chart.draw_curve(
|
||||||
fqsn,
|
fqsn,
|
||||||
hist_ohlcv,
|
hist_ohlcv,
|
||||||
flume,
|
flume,
|
||||||
|
@ -1292,7 +1324,7 @@ async def display_symbol_data(
|
||||||
|
|
||||||
# ensure the last datum graphic is generated
|
# ensure the last datum graphic is generated
|
||||||
# for zoom-interaction purposes.
|
# for zoom-interaction purposes.
|
||||||
viz.draw_last(array_key=fqsn)
|
hist_viz.draw_last(array_key=fqsn)
|
||||||
|
|
||||||
hist_pi.vb.maxmin = partial(
|
hist_pi.vb.maxmin = partial(
|
||||||
hist_chart.maxmin,
|
hist_chart.maxmin,
|
||||||
|
@ -1302,8 +1334,8 @@ async def display_symbol_data(
|
||||||
# specially store ref to shm for lookup in display loop
|
# specially store ref to shm for lookup in display loop
|
||||||
# since only a placeholder of `None` is entered in
|
# since only a placeholder of `None` is entered in
|
||||||
# ``.draw_curve()``.
|
# ``.draw_curve()``.
|
||||||
viz = hist_chart._vizs[fqsn]
|
hist_viz = hist_chart._vizs[fqsn]
|
||||||
assert viz.plot is hist_pi
|
assert hist_viz.plot is hist_pi
|
||||||
pis.setdefault(fqsn, [None, None])[1] = hist_pi
|
pis.setdefault(fqsn, [None, None])[1] = hist_pi
|
||||||
|
|
||||||
rt_pi = rt_chart.overlay_plotitem(
|
rt_pi = rt_chart.overlay_plotitem(
|
||||||
|
@ -1314,7 +1346,7 @@ async def display_symbol_data(
|
||||||
rt_pi.hideAxis('left')
|
rt_pi.hideAxis('left')
|
||||||
rt_pi.hideAxis('bottom')
|
rt_pi.hideAxis('bottom')
|
||||||
|
|
||||||
viz = rt_chart.draw_curve(
|
rt_viz = rt_chart.draw_curve(
|
||||||
fqsn,
|
fqsn,
|
||||||
ohlcv,
|
ohlcv,
|
||||||
flume,
|
flume,
|
||||||
|
@ -1335,8 +1367,8 @@ async def display_symbol_data(
|
||||||
# specially store ref to shm for lookup in display loop
|
# specially store ref to shm for lookup in display loop
|
||||||
# since only a placeholder of `None` is entered in
|
# since only a placeholder of `None` is entered in
|
||||||
# ``.draw_curve()``.
|
# ``.draw_curve()``.
|
||||||
viz = rt_chart._vizs[fqsn]
|
rt_viz = rt_chart._vizs[fqsn]
|
||||||
assert viz.plot is rt_pi
|
assert rt_viz.plot is rt_pi
|
||||||
pis.setdefault(fqsn, [None, None])[0] = rt_pi
|
pis.setdefault(fqsn, [None, None])[0] = rt_pi
|
||||||
|
|
||||||
rt_chart.setFocus()
|
rt_chart.setFocus()
|
||||||
|
@ -1404,17 +1436,16 @@ async def display_symbol_data(
|
||||||
|
|
||||||
rt_linked.mode = mode
|
rt_linked.mode = mode
|
||||||
|
|
||||||
viz = rt_chart.get_viz(order_ctl_symbol)
|
rt_viz = rt_chart.get_viz(order_ctl_symbol)
|
||||||
viz.plot.setFocus()
|
rt_viz.plot.setFocus()
|
||||||
|
|
||||||
# default view adjuments and sidepane alignment
|
# default view adjuments and sidepane alignment
|
||||||
# as final default UX touch.
|
# as final default UX touch.
|
||||||
rt_chart.default_view()
|
rt_chart.default_view()
|
||||||
rt_chart.view.enable_auto_yrange()
|
|
||||||
await trio.sleep(0)
|
await trio.sleep(0)
|
||||||
|
|
||||||
hist_chart.default_view()
|
hist_chart.default_view()
|
||||||
hist_chart.view.enable_auto_yrange()
|
hist_viz = hist_chart.get_viz(fqsn)
|
||||||
await trio.sleep(0)
|
await trio.sleep(0)
|
||||||
|
|
||||||
godwidget.resize_all()
|
godwidget.resize_all()
|
||||||
|
|
Loading…
Reference in New Issue