Proper slow chart auto y-range support
The slow (history) chart requires it's own y-range checker logic which needs to be run in 2 cases: - the last datum is in view and goes outside the previous mx/mn in view - the chart is incremented a step Since we need this duplicate logic this patch also factors the incremental graphics update info "reading" into a new `DisplayState.incr_info()` method that can be configured to a chart and input state and returns all relevant "graphics update measure" in a tuple (for now). Use this method throughout the rest of the display loop for both fast and slow chart checks and in the `increment_history_view()` slow chart task.history_view
							parent
							
								
									7958d8ad4f
								
							
						
					
					
						commit
						10c1944de5
					
				| 
						 | 
				
			
			@ -21,7 +21,6 @@ this module ties together quote and computational (fsp) streams with
 | 
			
		|||
graphics update methods via our custom ``pyqtgraph`` charting api.
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from functools import partial
 | 
			
		||||
import time
 | 
			
		||||
from typing import Optional, Any, Callable
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +34,7 @@ from ..data.feed import (
 | 
			
		|||
    open_feed,
 | 
			
		||||
    Feed,
 | 
			
		||||
)
 | 
			
		||||
from ..data.types import Struct
 | 
			
		||||
from ._axes import YAxisLabel
 | 
			
		||||
from ._chart import (
 | 
			
		||||
    ChartPlotWidget,
 | 
			
		||||
| 
						 | 
				
			
			@ -124,8 +124,7 @@ def chart_maxmin(
 | 
			
		|||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class DisplayState:
 | 
			
		||||
class DisplayState(Struct):
 | 
			
		||||
    '''
 | 
			
		||||
    Chart-local real-time graphics state container.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -146,12 +145,77 @@ class DisplayState:
 | 
			
		|||
    hist_last_price_sticky: YAxisLabel
 | 
			
		||||
 | 
			
		||||
    # misc state tracking
 | 
			
		||||
    vars: dict[str, Any]
 | 
			
		||||
    vars: dict[str, Any] = {
 | 
			
		||||
        'tick_margin': 0,
 | 
			
		||||
        'i_last': 0,
 | 
			
		||||
        'i_last_append': 0,
 | 
			
		||||
        'last_mx_vlm': 0,
 | 
			
		||||
        'last_mx': 0,
 | 
			
		||||
        'last_mn': 0,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vlm_chart: Optional[ChartPlotWidget] = None
 | 
			
		||||
    vlm_sticky: Optional[YAxisLabel] = None
 | 
			
		||||
    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,
 | 
			
		||||
 | 
			
		||||
    ) -> tuple:
 | 
			
		||||
 | 
			
		||||
        shm = shm or self.ohlcv
 | 
			
		||||
        chart = chart or self.chart
 | 
			
		||||
        state = state or self.vars
 | 
			
		||||
 | 
			
		||||
        if not update_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_diff = i_step - state['i_last']
 | 
			
		||||
        state['i_last'] = i_step
 | 
			
		||||
 | 
			
		||||
        append_diff = i_step - state['i_last_append']
 | 
			
		||||
 | 
			
		||||
        # update the "last datum" (aka extending the flow 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)
 | 
			
		||||
        if do_append:
 | 
			
		||||
            state['i_last_append'] = i_step
 | 
			
		||||
 | 
			
		||||
        do_rt_update = uppx < update_uppx
 | 
			
		||||
        _, _, _, r = chart.bars_range()
 | 
			
		||||
        liv = r >= i_step
 | 
			
		||||
 | 
			
		||||
        # TODO: pack this into a struct
 | 
			
		||||
        return (
 | 
			
		||||
            uppx,
 | 
			
		||||
            liv,
 | 
			
		||||
            do_append,
 | 
			
		||||
            i_diff,
 | 
			
		||||
            append_diff,
 | 
			
		||||
            do_rt_update,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def graphics_update_loop(
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +335,11 @@ async def graphics_update_loop(
 | 
			
		|||
    # API that can be reused at least in terms of pulling view
 | 
			
		||||
    # params (eg ``.bars_range()``).
 | 
			
		||||
    async def increment_history_view():
 | 
			
		||||
        i_last_append = i_last = hist_ohlcv.index
 | 
			
		||||
        i_last = hist_ohlcv.index
 | 
			
		||||
        state = ds.vars.copy() | {
 | 
			
		||||
            'i_last_append': i_last,
 | 
			
		||||
            'i_last': i_last,
 | 
			
		||||
        }
 | 
			
		||||
        _, hist_step_size_s, _ = feed.get_ds_info()
 | 
			
		||||
 | 
			
		||||
        async with feed.index_stream(
 | 
			
		||||
| 
						 | 
				
			
			@ -279,27 +347,26 @@ async def graphics_update_loop(
 | 
			
		|||
        ) as istream:
 | 
			
		||||
            async for msg in istream:
 | 
			
		||||
 | 
			
		||||
                # increment the view position by the sample offset.
 | 
			
		||||
                uppx = hist_chart.view.x_uppx()
 | 
			
		||||
                l, lbar, rbar, r = hist_chart.bars_range()
 | 
			
		||||
 | 
			
		||||
                i_step = hist_ohlcv.index
 | 
			
		||||
                i_diff = i_step - i_last
 | 
			
		||||
                i_last = i_step
 | 
			
		||||
                liv = r >= i_step
 | 
			
		||||
                append_diff = i_step - i_last_append
 | 
			
		||||
                do_append = (append_diff >= uppx)
 | 
			
		||||
 | 
			
		||||
                if do_append:
 | 
			
		||||
                    i_last_append = i_step
 | 
			
		||||
 | 
			
		||||
                # check if slow chart needs a resize
 | 
			
		||||
                (
 | 
			
		||||
                    uppx,
 | 
			
		||||
                    liv,
 | 
			
		||||
                    do_append,
 | 
			
		||||
                    i_diff,
 | 
			
		||||
                    append_diff,
 | 
			
		||||
                    do_rt_update,
 | 
			
		||||
                ) = ds.incr_info(
 | 
			
		||||
                    chart=hist_chart,
 | 
			
		||||
                    shm=ds.hist_ohlcv,
 | 
			
		||||
                    state=state,
 | 
			
		||||
                    # update_state=False,
 | 
			
		||||
                )
 | 
			
		||||
                if (
 | 
			
		||||
                    # i_diff > 0  # no new sample step
 | 
			
		||||
                    do_append
 | 
			
		||||
                    # and uppx < 4  # chart is zoomed out very far
 | 
			
		||||
                    and liv
 | 
			
		||||
                ):
 | 
			
		||||
                    hist_chart.increment_view(steps=i_diff)
 | 
			
		||||
                    hist_chart.view._set_yrange(yrange=hist_chart.maxmin())
 | 
			
		||||
 | 
			
		||||
    nurse.start_soon(increment_history_view)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -374,47 +441,15 @@ def graphics_update_cycle(
 | 
			
		|||
    vars = ds.vars
 | 
			
		||||
    tick_margin = vars['tick_margin']
 | 
			
		||||
 | 
			
		||||
    update_uppx = 16
 | 
			
		||||
 | 
			
		||||
    for sym, quote in ds.quotes.items():
 | 
			
		||||
 | 
			
		||||
        # compute the first available graphic's x-units-per-pixel
 | 
			
		||||
        uppx = chart.view.x_uppx()
 | 
			
		||||
 | 
			
		||||
        # NOTE: vlm may be written by the ``brokerd`` backend
 | 
			
		||||
        # event though a tick sample is not emitted.
 | 
			
		||||
        # TODO: show dark trades differently
 | 
			
		||||
        # https://github.com/pikers/piker/issues/116
 | 
			
		||||
 | 
			
		||||
        # 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 = ohlcv.index
 | 
			
		||||
        i_diff = i_step - vars['i_last']
 | 
			
		||||
        vars['i_last'] = i_step
 | 
			
		||||
 | 
			
		||||
        append_diff = i_step - vars['i_last_append']
 | 
			
		||||
 | 
			
		||||
        # update the "last datum" (aka extending the flow 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)
 | 
			
		||||
        if do_append:
 | 
			
		||||
            vars['i_last_append'] = i_step
 | 
			
		||||
 | 
			
		||||
        do_rt_update = uppx < update_uppx
 | 
			
		||||
        # print(
 | 
			
		||||
        #     f'append_diff:{append_diff}\n'
 | 
			
		||||
        #     f'uppx:{uppx}\n'
 | 
			
		||||
        #     f'do_append: {do_append}'
 | 
			
		||||
        # )
 | 
			
		||||
        (
 | 
			
		||||
            uppx,
 | 
			
		||||
            liv,
 | 
			
		||||
            do_append,
 | 
			
		||||
            i_diff,
 | 
			
		||||
            append_diff,
 | 
			
		||||
            do_rt_update,
 | 
			
		||||
        ) = ds.incr_info()
 | 
			
		||||
 | 
			
		||||
        # TODO: we should only run mxmn when we know
 | 
			
		||||
        # an update is due via ``do_append`` above.
 | 
			
		||||
| 
						 | 
				
			
			@ -430,8 +465,6 @@ def graphics_update_cycle(
 | 
			
		|||
 | 
			
		||||
        profiler('`ds.maxmin()` call')
 | 
			
		||||
 | 
			
		||||
        liv = r >= i_step  # the last datum is in view
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            prepend_update_index is not None
 | 
			
		||||
            and lbar > prepend_update_index
 | 
			
		||||
| 
						 | 
				
			
			@ -446,16 +479,10 @@ def graphics_update_cycle(
 | 
			
		|||
        # don't real-time "shift" the curve to the
 | 
			
		||||
        # left unless we get one of the following:
 | 
			
		||||
        if (
 | 
			
		||||
            (
 | 
			
		||||
                # i_diff > 0  # no new sample step
 | 
			
		||||
                do_append
 | 
			
		||||
                # and uppx < 4  # chart is zoomed out very far
 | 
			
		||||
                and liv
 | 
			
		||||
            )
 | 
			
		||||
            (do_append and liv)
 | 
			
		||||
            or trigger_all
 | 
			
		||||
        ):
 | 
			
		||||
            chart.increment_view(steps=i_diff)
 | 
			
		||||
            # chart.increment_view(steps=i_diff + round(append_diff - uppx))
 | 
			
		||||
 | 
			
		||||
            if vlm_chart:
 | 
			
		||||
                vlm_chart.increment_view(steps=i_diff)
 | 
			
		||||
| 
						 | 
				
			
			@ -606,10 +633,12 @@ def graphics_update_cycle(
 | 
			
		|||
                l1.bid_label.update_fields({'level': price, 'size': size})
 | 
			
		||||
 | 
			
		||||
        # check for y-range re-size
 | 
			
		||||
        if (mx > vars['last_mx']) or (mn < vars['last_mn']):
 | 
			
		||||
 | 
			
		||||
            # fast chart resize case
 | 
			
		||||
            if (
 | 
			
		||||
            (mx > vars['last_mx']) or (mn < vars['last_mn'])
 | 
			
		||||
                liv
 | 
			
		||||
                and not chart._static_yrange == 'axis'
 | 
			
		||||
            and liv
 | 
			
		||||
            ):
 | 
			
		||||
                main_vb = chart.view
 | 
			
		||||
                if (
 | 
			
		||||
| 
						 | 
				
			
			@ -627,6 +656,22 @@ def graphics_update_cycle(
 | 
			
		|||
                        yrange=(mn, mx),
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            # check if slow chart needs a resize
 | 
			
		||||
            (
 | 
			
		||||
                _,
 | 
			
		||||
                hist_liv,
 | 
			
		||||
                _,
 | 
			
		||||
                _,
 | 
			
		||||
                _,
 | 
			
		||||
                _,
 | 
			
		||||
            ) = ds.incr_info(
 | 
			
		||||
                chart=hist_chart,
 | 
			
		||||
                shm=ds.hist_ohlcv,
 | 
			
		||||
                update_state=False,
 | 
			
		||||
            )
 | 
			
		||||
            if hist_liv:
 | 
			
		||||
                hist_chart.view._set_yrange(yrange=hist_chart.maxmin())
 | 
			
		||||
 | 
			
		||||
        # XXX: update this every draw cycle to make L1-always-in-view work.
 | 
			
		||||
        vars['last_mx'], vars['last_mn'] = mx, mn
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue