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.
|
graphics update methods via our custom ``pyqtgraph`` charting api.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from dataclasses import dataclass
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Any, Callable
|
from typing import Optional, Any, Callable
|
||||||
|
@ -35,6 +34,7 @@ from ..data.feed import (
|
||||||
open_feed,
|
open_feed,
|
||||||
Feed,
|
Feed,
|
||||||
)
|
)
|
||||||
|
from ..data.types import Struct
|
||||||
from ._axes import YAxisLabel
|
from ._axes import YAxisLabel
|
||||||
from ._chart import (
|
from ._chart import (
|
||||||
ChartPlotWidget,
|
ChartPlotWidget,
|
||||||
|
@ -124,8 +124,7 @@ def chart_maxmin(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class DisplayState(Struct):
|
||||||
class DisplayState:
|
|
||||||
'''
|
'''
|
||||||
Chart-local real-time graphics state container.
|
Chart-local real-time graphics state container.
|
||||||
|
|
||||||
|
@ -146,12 +145,77 @@ class DisplayState:
|
||||||
hist_last_price_sticky: YAxisLabel
|
hist_last_price_sticky: YAxisLabel
|
||||||
|
|
||||||
# misc state tracking
|
# 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_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,
|
||||||
|
|
||||||
|
) -> 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(
|
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
|
# 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_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()
|
_, hist_step_size_s, _ = feed.get_ds_info()
|
||||||
|
|
||||||
async with feed.index_stream(
|
async with feed.index_stream(
|
||||||
|
@ -279,27 +347,26 @@ async def graphics_update_loop(
|
||||||
) as istream:
|
) as istream:
|
||||||
async for msg in istream:
|
async for msg in istream:
|
||||||
|
|
||||||
# increment the view position by the sample offset.
|
# check if slow chart needs a resize
|
||||||
uppx = hist_chart.view.x_uppx()
|
(
|
||||||
l, lbar, rbar, r = hist_chart.bars_range()
|
uppx,
|
||||||
|
liv,
|
||||||
i_step = hist_ohlcv.index
|
do_append,
|
||||||
i_diff = i_step - i_last
|
i_diff,
|
||||||
i_last = i_step
|
append_diff,
|
||||||
liv = r >= i_step
|
do_rt_update,
|
||||||
append_diff = i_step - i_last_append
|
) = ds.incr_info(
|
||||||
do_append = (append_diff >= uppx)
|
chart=hist_chart,
|
||||||
|
shm=ds.hist_ohlcv,
|
||||||
if do_append:
|
state=state,
|
||||||
i_last_append = i_step
|
# update_state=False,
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
# i_diff > 0 # no new sample step
|
|
||||||
do_append
|
do_append
|
||||||
# and uppx < 4 # chart is zoomed out very far
|
|
||||||
and liv
|
and liv
|
||||||
):
|
):
|
||||||
hist_chart.increment_view(steps=i_diff)
|
hist_chart.increment_view(steps=i_diff)
|
||||||
|
hist_chart.view._set_yrange(yrange=hist_chart.maxmin())
|
||||||
|
|
||||||
nurse.start_soon(increment_history_view)
|
nurse.start_soon(increment_history_view)
|
||||||
|
|
||||||
|
@ -374,47 +441,15 @@ def graphics_update_cycle(
|
||||||
vars = ds.vars
|
vars = ds.vars
|
||||||
tick_margin = vars['tick_margin']
|
tick_margin = vars['tick_margin']
|
||||||
|
|
||||||
update_uppx = 16
|
|
||||||
|
|
||||||
for sym, quote in ds.quotes.items():
|
for sym, quote in ds.quotes.items():
|
||||||
|
(
|
||||||
# compute the first available graphic's x-units-per-pixel
|
uppx,
|
||||||
uppx = chart.view.x_uppx()
|
liv,
|
||||||
|
do_append,
|
||||||
# NOTE: vlm may be written by the ``brokerd`` backend
|
i_diff,
|
||||||
# event though a tick sample is not emitted.
|
append_diff,
|
||||||
# TODO: show dark trades differently
|
do_rt_update,
|
||||||
# https://github.com/pikers/piker/issues/116
|
) = ds.incr_info()
|
||||||
|
|
||||||
# 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}'
|
|
||||||
# )
|
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -430,8 +465,6 @@ def graphics_update_cycle(
|
||||||
|
|
||||||
profiler('`ds.maxmin()` call')
|
profiler('`ds.maxmin()` call')
|
||||||
|
|
||||||
liv = r >= i_step # the last datum is in view
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prepend_update_index is not None
|
prepend_update_index is not None
|
||||||
and lbar > prepend_update_index
|
and lbar > prepend_update_index
|
||||||
|
@ -446,16 +479,10 @@ def graphics_update_cycle(
|
||||||
# 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 (
|
||||||
(
|
(do_append and liv)
|
||||||
# i_diff > 0 # no new sample step
|
|
||||||
do_append
|
|
||||||
# and uppx < 4 # chart is zoomed out very far
|
|
||||||
and liv
|
|
||||||
)
|
|
||||||
or trigger_all
|
or trigger_all
|
||||||
):
|
):
|
||||||
chart.increment_view(steps=i_diff)
|
chart.increment_view(steps=i_diff)
|
||||||
# chart.increment_view(steps=i_diff + round(append_diff - uppx))
|
|
||||||
|
|
||||||
if vlm_chart:
|
if vlm_chart:
|
||||||
vlm_chart.increment_view(steps=i_diff)
|
vlm_chart.increment_view(steps=i_diff)
|
||||||
|
@ -606,26 +633,44 @@ def graphics_update_cycle(
|
||||||
l1.bid_label.update_fields({'level': price, 'size': size})
|
l1.bid_label.update_fields({'level': price, 'size': size})
|
||||||
|
|
||||||
# check for y-range re-size
|
# check for y-range re-size
|
||||||
if (
|
if (mx > vars['last_mx']) or (mn < vars['last_mn']):
|
||||||
(mx > vars['last_mx']) or (mn < vars['last_mn'])
|
|
||||||
and not chart._static_yrange == 'axis'
|
# fast chart resize case
|
||||||
and liv
|
|
||||||
):
|
|
||||||
main_vb = chart.view
|
|
||||||
if (
|
if (
|
||||||
main_vb._ic is None
|
liv
|
||||||
or not main_vb._ic.is_set()
|
and not chart._static_yrange == 'axis'
|
||||||
):
|
):
|
||||||
# print(f'updating range due to mxmn')
|
main_vb = chart.view
|
||||||
main_vb._set_yrange(
|
if (
|
||||||
# TODO: we should probably scale
|
main_vb._ic is None
|
||||||
# the view margin based on the size
|
or not main_vb._ic.is_set()
|
||||||
# of the true range? This way you can
|
):
|
||||||
# slap in orders outside the current
|
# print(f'updating range due to mxmn')
|
||||||
# L1 (only) book range.
|
main_vb._set_yrange(
|
||||||
# range_margin=0.1,
|
# TODO: we should probably scale
|
||||||
yrange=(mn, mx),
|
# the view margin based on the size
|
||||||
)
|
# of the true range? This way you can
|
||||||
|
# slap in orders outside the current
|
||||||
|
# L1 (only) book range.
|
||||||
|
# range_margin=0.1,
|
||||||
|
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.
|
# XXX: update this every draw cycle to make L1-always-in-view work.
|
||||||
vars['last_mx'], vars['last_mn'] = mx, mn
|
vars['last_mx'], vars['last_mn'] = mx, mn
|
||||||
|
|
Loading…
Reference in New Issue