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.overlays_interaction_latency_tuning
							parent
							
								
									9780263cfa
								
							
						
					
					
						commit
						8b5b1c214b
					
				| 
						 | 
				
			
			@ -25,7 +25,10 @@ from functools import partial
 | 
			
		|||
import itertools
 | 
			
		||||
from math import floor
 | 
			
		||||
import time
 | 
			
		||||
from typing import Optional, Any, Callable
 | 
			
		||||
from typing import (
 | 
			
		||||
    Optional,
 | 
			
		||||
    Any,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import tractor
 | 
			
		||||
import trio
 | 
			
		||||
| 
						 | 
				
			
			@ -87,10 +90,11 @@ log = get_logger(__name__)
 | 
			
		|||
# approach, likely with ``numba``:
 | 
			
		||||
# https://arxiv.org/abs/cs/0610046
 | 
			
		||||
# https://github.com/lemire/pythonmaxmin
 | 
			
		||||
def chart_maxmin(
 | 
			
		||||
def multi_maxmin(
 | 
			
		||||
    i_read_range: tuple[int, int] | None,
 | 
			
		||||
    fast_viz: Viz,
 | 
			
		||||
    fqsn: str,
 | 
			
		||||
    vlm_viz: Viz | None = None,
 | 
			
		||||
    profiler: Profiler = None,
 | 
			
		||||
 | 
			
		||||
) -> tuple[
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -104,26 +108,32 @@ def chart_maxmin(
 | 
			
		|||
    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:
 | 
			
		||||
        # log.warning(f'No yrange provided for {name}!?')
 | 
			
		||||
        return (0, 0, 0)
 | 
			
		||||
 | 
			
		||||
    (
 | 
			
		||||
        ixrng,
 | 
			
		||||
        read_slc,
 | 
			
		||||
        mxmn,
 | 
			
		||||
        yrange,
 | 
			
		||||
    ) = 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
 | 
			
		||||
    # np.max/min trigger and especially on the vlm_chart
 | 
			
		||||
    # vizs which aren't shown.. like vlm?
 | 
			
		||||
    mx_vlm_in_view = 0
 | 
			
		||||
    if vlm_viz:
 | 
			
		||||
        out = vlm_viz.maxmin()
 | 
			
		||||
        out = vlm_viz.maxmin(
 | 
			
		||||
            i_read_range=i_read_range,
 | 
			
		||||
        )
 | 
			
		||||
        if out:
 | 
			
		||||
            (
 | 
			
		||||
                ixrng,
 | 
			
		||||
| 
						 | 
				
			
			@ -132,9 +142,16 @@ def chart_maxmin(
 | 
			
		|||
            ) = out
 | 
			
		||||
            mx_vlm_in_view = mxmn[1]
 | 
			
		||||
 | 
			
		||||
        if profiler:
 | 
			
		||||
            profiler('vlm_viz.maxmin({read_slc})')
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        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
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +165,6 @@ class DisplayState(Struct):
 | 
			
		|||
    godwidget: GodWidget
 | 
			
		||||
    quotes: dict[str, Any]
 | 
			
		||||
 | 
			
		||||
    maxmin: Callable
 | 
			
		||||
    flume: Flume
 | 
			
		||||
 | 
			
		||||
    # high level chart handles and underlying ``Viz``
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +178,8 @@ class DisplayState(Struct):
 | 
			
		|||
    last_price_sticky: YAxisLabel
 | 
			
		||||
    hist_last_price_sticky: YAxisLabel
 | 
			
		||||
 | 
			
		||||
    vlm_viz: Viz
 | 
			
		||||
 | 
			
		||||
    # misc state tracking
 | 
			
		||||
    vars: dict[str, Any] = field(
 | 
			
		||||
        default_factory=lambda: {
 | 
			
		||||
| 
						 | 
				
			
			@ -238,21 +256,22 @@ async def increment_history_view(
 | 
			
		|||
                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
 | 
			
		||||
            if should_tread:
 | 
			
		||||
                # ensure path graphics append is shown on treads since
 | 
			
		||||
                # 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)
 | 
			
		||||
                profiler('hist tread view')
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                do_px_step
 | 
			
		||||
                and liv
 | 
			
		||||
            ):
 | 
			
		||||
                hist_viz.plot.vb._set_yrange(viz=hist_viz)
 | 
			
		||||
            profiler.finish()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def graphics_update_loop(
 | 
			
		||||
| 
						 | 
				
			
			@ -331,17 +350,15 @@ async def graphics_update_loop(
 | 
			
		|||
        vlm_chart = vlm_charts[fqsn]
 | 
			
		||||
        vlm_viz = vlm_chart._vizs.get('volume') if vlm_chart else None
 | 
			
		||||
 | 
			
		||||
        maxmin = partial(
 | 
			
		||||
            chart_maxmin,
 | 
			
		||||
            fast_viz,
 | 
			
		||||
            fqsn,
 | 
			
		||||
            vlm_viz,
 | 
			
		||||
        )
 | 
			
		||||
        (
 | 
			
		||||
            last_mx,
 | 
			
		||||
            last_mn,
 | 
			
		||||
            last_mx_vlm,
 | 
			
		||||
        ) = maxmin()
 | 
			
		||||
        ) = multi_maxmin(
 | 
			
		||||
            None,
 | 
			
		||||
            fast_viz,
 | 
			
		||||
            vlm_viz,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        last, volume = ohlcv.array[-1][['close', 'volume']]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -372,7 +389,7 @@ async def graphics_update_loop(
 | 
			
		|||
            'fqsn': fqsn,
 | 
			
		||||
            'godwidget': godwidget,
 | 
			
		||||
            'quotes': {},
 | 
			
		||||
            'maxmin': maxmin,
 | 
			
		||||
            # 'maxmin': maxmin,
 | 
			
		||||
 | 
			
		||||
            'flume': flume,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -384,6 +401,8 @@ async def graphics_update_loop(
 | 
			
		|||
            'hist_viz': hist_viz,
 | 
			
		||||
            'hist_last_price_sticky': hist_last_price_sticky,
 | 
			
		||||
 | 
			
		||||
            'vlm_viz': vlm_viz,
 | 
			
		||||
 | 
			
		||||
            'l1': l1,
 | 
			
		||||
 | 
			
		||||
            'vars': {
 | 
			
		||||
| 
						 | 
				
			
			@ -491,11 +510,10 @@ def graphics_update_cycle(
 | 
			
		|||
    #   or at least a little `mypyc` B)
 | 
			
		||||
    # - pass more direct refs as input to avoid so many attr accesses?
 | 
			
		||||
    # - 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
 | 
			
		||||
    chart = ds.chart
 | 
			
		||||
    hist_chart = ds.hist_chart
 | 
			
		||||
    vlm_chart = ds.vlm_chart
 | 
			
		||||
 | 
			
		||||
    varz = ds.vars
 | 
			
		||||
| 
						 | 
				
			
			@ -519,25 +537,19 @@ def graphics_update_cycle(
 | 
			
		|||
        do_rt_update,
 | 
			
		||||
        should_tread,
 | 
			
		||||
    ) = main_viz.incr_info(ds=ds)
 | 
			
		||||
 | 
			
		||||
    profiler('`.incr_info()`')
 | 
			
		||||
 | 
			
		||||
    # TODO: we should only run mxmn when we know
 | 
			
		||||
    # 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
 | 
			
		||||
    # them as an additional graphic.
 | 
			
		||||
    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
 | 
			
		||||
    if (
 | 
			
		||||
        # do_rt_update
 | 
			
		||||
| 
						 | 
				
			
			@ -545,9 +557,24 @@ def graphics_update_cycle(
 | 
			
		|||
        (liv and do_px_step)
 | 
			
		||||
        or trigger_all
 | 
			
		||||
    ):
 | 
			
		||||
        main_viz.update_graphics(array_key=fqsn)
 | 
			
		||||
        i_read_range, _ = main_viz.update_graphics()
 | 
			
		||||
        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
 | 
			
		||||
        # left unless we get one of the following:
 | 
			
		||||
        if (
 | 
			
		||||
| 
						 | 
				
			
			@ -717,15 +744,21 @@ def graphics_update_cycle(
 | 
			
		|||
            is_1m=True,
 | 
			
		||||
        )
 | 
			
		||||
        profiler('hist `Viz.incr_info()`')
 | 
			
		||||
        if (
 | 
			
		||||
            hist_liv
 | 
			
		||||
            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')
 | 
			
		||||
 | 
			
		||||
        # TODO: track local liv maxmin without doing a recompute all the
 | 
			
		||||
        # time..plut, just generally the user is more likely to be
 | 
			
		||||
        # zoomed out enough on the slow chart that this is never an
 | 
			
		||||
        # issue (the last datum going out of y-range).
 | 
			
		||||
        # hist_chart = ds.hist_chart
 | 
			
		||||
        # if (
 | 
			
		||||
        #     hist_liv
 | 
			
		||||
        #     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
 | 
			
		||||
    # only adjusts when the in-view data co-domain actually expands or
 | 
			
		||||
| 
						 | 
				
			
			@ -816,10 +849,10 @@ def graphics_update_cycle(
 | 
			
		|||
            if (
 | 
			
		||||
                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
 | 
			
		||||
                # 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
 | 
			
		||||
        for curve_name, viz in vlm_vizs.items():
 | 
			
		||||
| 
						 | 
				
			
			@ -845,13 +878,12 @@ def graphics_update_cycle(
 | 
			
		|||
                # is this even doing anything?
 | 
			
		||||
                # (pretty sure it's the real-time
 | 
			
		||||
                # resizing from last quote?)
 | 
			
		||||
                fvb = viz.plot.vb
 | 
			
		||||
 | 
			
		||||
                # XXX: without this we get completely
 | 
			
		||||
                # mangled/empty vlm display subchart..
 | 
			
		||||
                fvb._set_yrange(
 | 
			
		||||
                    viz=viz,
 | 
			
		||||
                )
 | 
			
		||||
                # fvb = viz.plot.vb
 | 
			
		||||
                # fvb._set_yrange(
 | 
			
		||||
                #     viz=viz,
 | 
			
		||||
                # )
 | 
			
		||||
                profiler(f'vlm `Viz[{viz.name}].plot.vb._set_yrange()`')
 | 
			
		||||
 | 
			
		||||
            # even if we're downsampled bigly
 | 
			
		||||
| 
						 | 
				
			
			@ -1195,9 +1227,8 @@ async def display_symbol_data(
 | 
			
		|||
 | 
			
		||||
        # ensure the last datum graphic is generated
 | 
			
		||||
        # for zoom-interaction purposes.
 | 
			
		||||
        hist_chart.get_viz(fqsn).draw_last(
 | 
			
		||||
            array_key=fqsn,
 | 
			
		||||
        )
 | 
			
		||||
        hist_viz = hist_chart.get_viz(fqsn)
 | 
			
		||||
        hist_viz.draw_last(array_key=fqsn)
 | 
			
		||||
        pis.setdefault(fqsn, [None, None])[1] = hist_chart.plotItem
 | 
			
		||||
 | 
			
		||||
        # don't show when not focussed
 | 
			
		||||
| 
						 | 
				
			
			@ -1211,6 +1242,7 @@ async def display_symbol_data(
 | 
			
		|||
            # to avoid internal pane creation.
 | 
			
		||||
            sidepane=pp_pane,
 | 
			
		||||
        )
 | 
			
		||||
        rt_viz = rt_chart.get_viz(fqsn)
 | 
			
		||||
        pis.setdefault(fqsn, [None, None])[0] = rt_chart.plotItem
 | 
			
		||||
 | 
			
		||||
        # for pause/resume on mouse interaction
 | 
			
		||||
| 
						 | 
				
			
			@ -1227,7 +1259,7 @@ async def display_symbol_data(
 | 
			
		|||
                and has_vlm(ohlcv)
 | 
			
		||||
                and vlm_chart is None
 | 
			
		||||
            ):
 | 
			
		||||
                vlm_charts[fqsn] = await ln.start(
 | 
			
		||||
                vlm_chart = vlm_charts[fqsn] = await ln.start(
 | 
			
		||||
                    open_vlm_displays,
 | 
			
		||||
                    rt_linked,
 | 
			
		||||
                    flume,
 | 
			
		||||
| 
						 | 
				
			
			@ -1283,7 +1315,7 @@ async def display_symbol_data(
 | 
			
		|||
                # are none?
 | 
			
		||||
                hist_pi.hideAxis('left')
 | 
			
		||||
 | 
			
		||||
                viz = hist_chart.draw_curve(
 | 
			
		||||
                hist_viz = hist_chart.draw_curve(
 | 
			
		||||
                    fqsn,
 | 
			
		||||
                    hist_ohlcv,
 | 
			
		||||
                    flume,
 | 
			
		||||
| 
						 | 
				
			
			@ -1298,7 +1330,7 @@ async def display_symbol_data(
 | 
			
		|||
 | 
			
		||||
                # ensure the last datum graphic is generated
 | 
			
		||||
                # for zoom-interaction purposes.
 | 
			
		||||
                viz.draw_last(array_key=fqsn)
 | 
			
		||||
                hist_viz.draw_last(array_key=fqsn)
 | 
			
		||||
 | 
			
		||||
                hist_pi.vb.maxmin = partial(
 | 
			
		||||
                    hist_chart.maxmin,
 | 
			
		||||
| 
						 | 
				
			
			@ -1308,8 +1340,8 @@ async def display_symbol_data(
 | 
			
		|||
                # specially store ref to shm for lookup in display loop
 | 
			
		||||
                # since only a placeholder of `None` is entered in
 | 
			
		||||
                # ``.draw_curve()``.
 | 
			
		||||
                viz = hist_chart._vizs[fqsn]
 | 
			
		||||
                assert viz.plot is hist_pi
 | 
			
		||||
                hist_viz = hist_chart._vizs[fqsn]
 | 
			
		||||
                assert hist_viz.plot is hist_pi
 | 
			
		||||
                pis.setdefault(fqsn, [None, None])[1] = hist_pi
 | 
			
		||||
 | 
			
		||||
                rt_pi = rt_chart.overlay_plotitem(
 | 
			
		||||
| 
						 | 
				
			
			@ -1320,7 +1352,7 @@ async def display_symbol_data(
 | 
			
		|||
                rt_pi.hideAxis('left')
 | 
			
		||||
                rt_pi.hideAxis('bottom')
 | 
			
		||||
 | 
			
		||||
                viz = rt_chart.draw_curve(
 | 
			
		||||
                rt_viz = rt_chart.draw_curve(
 | 
			
		||||
                    fqsn,
 | 
			
		||||
                    ohlcv,
 | 
			
		||||
                    flume,
 | 
			
		||||
| 
						 | 
				
			
			@ -1341,8 +1373,8 @@ async def display_symbol_data(
 | 
			
		|||
                # specially store ref to shm for lookup in display loop
 | 
			
		||||
                # since only a placeholder of `None` is entered in
 | 
			
		||||
                # ``.draw_curve()``.
 | 
			
		||||
                viz = rt_chart._vizs[fqsn]
 | 
			
		||||
                assert viz.plot is rt_pi
 | 
			
		||||
                rt_viz = rt_chart._vizs[fqsn]
 | 
			
		||||
                assert rt_viz.plot is rt_pi
 | 
			
		||||
                pis.setdefault(fqsn, [None, None])[0] = rt_pi
 | 
			
		||||
 | 
			
		||||
            rt_chart.setFocus()
 | 
			
		||||
| 
						 | 
				
			
			@ -1410,17 +1442,16 @@ async def display_symbol_data(
 | 
			
		|||
 | 
			
		||||
                rt_linked.mode = mode
 | 
			
		||||
 | 
			
		||||
                viz = rt_chart.get_viz(order_ctl_symbol)
 | 
			
		||||
                viz.plot.setFocus()
 | 
			
		||||
                rt_viz = rt_chart.get_viz(order_ctl_symbol)
 | 
			
		||||
                rt_viz.plot.setFocus()
 | 
			
		||||
 | 
			
		||||
                # default view adjuments and sidepane alignment
 | 
			
		||||
                # as final default UX touch.
 | 
			
		||||
                rt_chart.default_view()
 | 
			
		||||
                rt_chart.view.enable_auto_yrange()
 | 
			
		||||
                await trio.sleep(0)
 | 
			
		||||
 | 
			
		||||
                hist_chart.default_view()
 | 
			
		||||
                hist_chart.view.enable_auto_yrange()
 | 
			
		||||
                hist_viz = hist_chart.get_viz(fqsn)
 | 
			
		||||
                await trio.sleep(0)
 | 
			
		||||
 | 
			
		||||
                godwidget.resize_all()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue