Fix overlayed slow chart "treading"

Turns out we were updating the wrong ``Viz``/``DisplayState`` inside the
closure style `increment_history_view()`` (probably due to looping
through the flumes and dynamically closing in that task-func).. Instead
define the history incrementer at module level and pass in the
`DisplayState` explicitly. Further rework the `DisplayState` attrs to be
more focused around the `Viz` associated with the fast and slow chart
and be sure to adjust output from each `Viz.incr_info()` call to latest
update. Oh, and just tweaked the line palette for the moment.

FYI "treading" here is referring to  the x-shifting of the curve when
the last datum is in view such that on new sampled appends the "last"
datum is kept in the same x-location in UI terms.
multichartz
Tyler Goodlet 2022-12-19 17:11:34 -05:00
parent c509eff273
commit c53cc5f384
1 changed files with 101 additions and 109 deletions

View File

@ -41,12 +41,14 @@ from ..data.types import Struct
from ..data._sharedmem import ( from ..data._sharedmem import (
ShmArray, ShmArray,
) )
from ..data._sampling import _tick_groups
from ._axes import YAxisLabel from ._axes import YAxisLabel
from ._chart import ( from ._chart import (
ChartPlotWidget, ChartPlotWidget,
LinkedSplits, LinkedSplits,
GodWidget, GodWidget,
) )
from ._dataviz import Viz
from ._l1 import L1Labels from ._l1 import L1Labels
from ._style import hcolor from ._style import hcolor
from ._fsp import ( from ._fsp import (
@ -61,7 +63,6 @@ from ._forms import (
) )
from . import _pg_overrides as pgo from . import _pg_overrides as pgo
# from ..data._source import tf_in_1s # from ..data._source import tf_in_1s
from ..data._sampling import _tick_groups
from .order_mode import ( from .order_mode import (
open_order_mode, open_order_mode,
OrderMode, OrderMode,
@ -136,11 +137,12 @@ class DisplayState(Struct):
maxmin: Callable maxmin: Callable
flume: Flume flume: Flume
ohlcv: ShmArray
hist_ohlcv: ShmArray
# high level chart handles # high level chart handles and underlying ``Viz``
chart: ChartPlotWidget chart: ChartPlotWidget
viz: Viz
hist_chart: ChartPlotWidget
hist_viz: Viz
# axis labels # axis labels
l1: L1Labels l1: L1Labels
@ -172,6 +174,56 @@ class DisplayState(Struct):
wap_in_history: bool = False wap_in_history: bool = False
async def increment_history_view(
ds: DisplayState,
):
hist_chart = ds.hist_chart
hist_viz = ds.hist_viz
assert 'hist' in hist_viz.shm.token['shm_name']
# TODO: seems this is more reliable at keeping the slow
# chart incremented in view more correctly?
# - It might make sense to just inline this logic with the
# main display task? => it's a tradeoff of slower task
# wakeups/ctx switches verus logic checks (as normal)
# - we need increment logic that only does the view shift
# call when the uppx permits/needs it
async with hist_viz.flume.index_stream(int(1)) as istream:
async for msg in istream:
# l3 = ds.viz.shm.array[-3:]
# print(
# f'fast step for {ds.flume.symbol.fqsn}:\n'
# f'{list(l3["time"])}\n'
# f'{l3}\n'
# )
# check if slow chart needs an x-domain shift and/or
# y-range resize.
(
uppx,
liv,
do_append,
i_diff_t,
append_diff,
do_rt_update,
should_tread,
) = hist_viz.incr_info(
ds=ds,
is_1m=True,
)
if (
do_append
and liv
):
hist_viz.plot.vb._set_yrange()
# check if tread-in-place x-shift is needed
if should_tread:
hist_chart.increment_view(datums=append_diff)
async def graphics_update_loop( async def graphics_update_loop(
nurse: trio.Nursery, nurse: trio.Nursery,
@ -210,8 +262,8 @@ async def graphics_update_loop(
# per-multichart-set such that automatic x-domain shifts are only # per-multichart-set such that automatic x-domain shifts are only
# done once per time step update. # done once per time step update.
globalz = { globalz = {
'i_last': 0, # multiview-global fast (1s) step index 'i_last_t': 0, # multiview-global fast (1s) step index
'i_last_slow': 0, # multiview-global slow (1m) step index 'i_last_slow_t': 0, # multiview-global slow (1m) step index
} }
dss: dict[str, DisplayState] = {} dss: dict[str, DisplayState] = {}
@ -287,25 +339,29 @@ async def graphics_update_loop(
tick_margin = 3 * tick_size tick_margin = 3 * tick_size
fast_chart.show() fast_chart.show()
last_quote = time.time() last_quote_s = time.time()
i_last: float = 0
dss[fqsn] = ds = linked.display_state = DisplayState(**{ dss[fqsn] = ds = linked.display_state = DisplayState(**{
'godwidget': godwidget, 'godwidget': godwidget,
'quotes': {}, 'quotes': {},
'maxmin': maxmin, 'maxmin': maxmin,
'flume': flume, 'flume': flume,
'ohlcv': ohlcv,
'hist_ohlcv': hist_ohlcv,
'chart': fast_chart, 'chart': fast_chart,
'viz': fast_viz,
'last_price_sticky': last_price_sticky, 'last_price_sticky': last_price_sticky,
'hist_chart': hist_chart,
'hist_viz': hist_viz,
'hist_last_price_sticky': hist_last_price_sticky, 'hist_last_price_sticky': hist_last_price_sticky,
'l1': l1, 'l1': l1,
'vars': { 'vars': {
'tick_margin': tick_margin, 'tick_margin': tick_margin,
'i_last': i_last, 'i_last': 0,
'i_last_append': i_last, 'i_last_append': 0,
'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,
@ -321,72 +377,25 @@ async def graphics_update_loop(
fast_chart.default_view() fast_chart.default_view()
ds.hist_vars.update({ # ds.hist_vars.update({
'i_last_append': i_last, # 'i_last_append': 0,
'i_last': i_last, # 'i_last': 0,
}) # })
# TODO: probably factor this into some kinda `DisplayState` nurse.start_soon(
# API that can be reused at least in terms of pulling view increment_history_view,
# params (eg ``.bars_range()``). ds,
async def increment_history_view(): )
_, hist_step_size_s, _ = flume.get_ds_info()
async with flume.index_stream( if ds.hist_vars['i_last'] < ds.hist_vars['i_last_append']:
# int(hist_step_size_s) breakpoint()
# TODO: seems this is more reliable at keeping the slow
# chart incremented in view more correctly?
# - It might make sense to just inline this logic with the
# main display task? => it's a tradeoff of slower task
# wakeups/ctx switches verus logic checks (as normal)
# - we need increment logic that only does the view shift
# call when the uppx permits/needs it
int(1),
) as istream:
async for msg in istream:
# check if slow chart needs an x-domain shift and/or
# y-range resize.
(
uppx,
liv,
do_append,
i_diff,
append_diff,
do_rt_update,
should_incr,
) = hist_viz.incr_info(
state=ds,
is_1m=True,
)
# print(
# f'liv: {liv}\n'
# f'do_append: {do_append}\n'
# f'append_diff: {append_diff}\n'
# )
if (
do_append
and liv
):
viz = hist_chart._vizs[fqsn]
viz.plot.vb._set_yrange(
# yrange=hist_chart.maxmin(name=fqsn)
)
# hist_chart.view._set_yrange(yrange=hist_chart.maxmin())
if should_incr:
hist_chart.increment_view(steps=i_diff)
nurse.start_soon(increment_history_view)
# main real-time quotes update loop # main real-time quotes update loop
stream: tractor.MsgStream stream: tractor.MsgStream
async with feed.open_multi_stream() as stream: async with feed.open_multi_stream() as stream:
assert stream assert stream
async for quotes in stream: async for quotes in stream:
quote_period = time.time() - last_quote quote_period = time.time() - last_quote_s
quote_rate = round( quote_rate = round(
1/quote_period, 1) if quote_period > 0 else float('inf') 1/quote_period, 1) if quote_period > 0 else float('inf')
if ( if (
@ -399,7 +408,7 @@ async def graphics_update_loop(
): ):
log.warning(f'High quote rate {symbol.key}: {quote_rate}') log.warning(f'High quote rate {symbol.key}: {quote_rate}')
last_quote = time.time() last_quote_s = time.time()
for sym, quote in quotes.items(): for sym, quote in quotes.items():
ds = dss[sym] ds = dss[sym]
@ -467,22 +476,21 @@ def graphics_update_cycle(
# rt "HFT" chart # rt "HFT" chart
l1 = ds.l1 l1 = ds.l1
# ohlcv = ds.ohlcv
ohlcv = flume.rt_shm ohlcv = flume.rt_shm
array = ohlcv.array array = ohlcv.array
vars = ds.vars varz = ds.vars
tick_margin = vars['tick_margin'] tick_margin = varz['tick_margin']
( (
uppx, uppx,
liv, liv,
do_append, do_append,
i_diff, i_diff_t,
append_diff, append_diff,
do_rt_update, do_rt_update,
should_incr, should_tread,
) = main_viz.incr_info(state=ds) ) = main_viz.incr_info(ds=ds)
# 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.
@ -497,20 +505,8 @@ def graphics_update_cycle(
mn = mn_in_view - tick_margin mn = mn_in_view - tick_margin
profiler('`ds.maxmin()` call') profiler('`ds.maxmin()` call')
if ( # TODO: eventually we want to separate out the dark vlm and show
prepend_update_index is not None # them as an additional graphic.
and lbar > prepend_update_index
):
# on a history update (usually from the FSP subsys)
# if the segment of history that is being prepended
# isn't in view there is no reason to do a graphics
# update.
log.info('Skipping prepend graphics cycle: frame not in view')
return
# TODO: eventually we want to separate out the utrade (aka
# dark vlm prices) here and show them as an additional
# graphic.
clear_types = _tick_groups['clears'] clear_types = _tick_groups['clears']
# update ohlc sampled price bars # update ohlc sampled price bars
@ -536,22 +532,19 @@ def graphics_update_cycle(
# left unless we get one of the following: # left unless we get one of the following:
if ( if (
( (
should_incr should_tread
and do_append and do_append
and liv and liv
) )
or trigger_all or trigger_all
): ):
# print(f'INCREMENTING {fqsn}') chart.increment_view(datums=append_diff)
chart.increment_view(steps=i_diff) main_viz.plot.vb._set_yrange()
main_viz.plot.vb._set_yrange(
# yrange=(mn, mx),
)
# NOTE: since vlm and ohlc charts are axis linked now we don't # NOTE: since vlm and ohlc charts are axis linked now we don't
# need the double increment request? # need the double increment request?
# if vlm_chart: # if vlm_chart:
# vlm_chart.increment_view(steps=i_diff) # vlm_chart.increment_view(datums=append_diff)
profiler('view incremented') profiler('view incremented')
@ -650,7 +643,7 @@ 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 (mx > vars['last_mx']) or (mn < vars['last_mn']): if (mx > varz['last_mx']) or (mn < varz['last_mn']):
# fast chart resize case # fast chart resize case
if ( if (
@ -686,16 +679,14 @@ def graphics_update_cycle(
_, _,
_, _,
) = hist_viz.incr_info( ) = hist_viz.incr_info(
state=ds, ds=ds,
is_1m=True, is_1m=True,
) )
if hist_liv: if hist_liv:
hist_viz.plot.vb._set_yrange( hist_viz.plot.vb._set_yrange()
# yrange=hist_chart.maxmin(name=fqsn),
)
# 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 varz['last_mx'], varz['last_mn'] = mx, mn
# run synchronous update on all linked viz # run synchronous update on all linked viz
# TODO: should the "main" (aka source) viz be special? # TODO: should the "main" (aka source) viz be special?
@ -724,7 +715,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..
@ -768,7 +759,7 @@ def graphics_update_cycle(
profiler('`vlm_chart.update_graphics_from_flow()`') profiler('`vlm_chart.update_graphics_from_flow()`')
if ( if (
mx_vlm_in_view != vars['last_mx_vlm'] mx_vlm_in_view != varz['last_mx_vlm']
): ):
yrange = (0, mx_vlm_in_view * 1.375) yrange = (0, mx_vlm_in_view * 1.375)
vlm_chart.view._set_yrange( vlm_chart.view._set_yrange(
@ -776,7 +767,7 @@ def graphics_update_cycle(
) )
profiler('`vlm_chart.view._set_yrange()`') profiler('`vlm_chart.view._set_yrange()`')
# print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}') # print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}')
vars['last_mx_vlm'] = mx_vlm_in_view varz['last_mx_vlm'] = mx_vlm_in_view
# update all downstream FSPs # update all downstream FSPs
for curve_name, viz in vlm_vizs.items(): for curve_name, viz in vlm_vizs.items():
@ -1108,10 +1099,11 @@ async def display_symbol_data(
# - gradient in "lightness" based on liquidity, or lifetime in derivs? # - gradient in "lightness" based on liquidity, or lifetime in derivs?
palette = itertools.cycle([ palette = itertools.cycle([
# curve color, last bar curve color # curve color, last bar curve color
['i3', 'gray'],
['grayer', 'bracket'],
['grayest', 'i3'], ['grayest', 'i3'],
['default_dark', 'default'], ['default_dark', 'default'],
['grayer', 'bracket'],
['i3', 'gray'],
]) ])
pis: dict[str, list[pgo.PlotItem, pgo.PlotItem]] = {} pis: dict[str, list[pgo.PlotItem, pgo.PlotItem]] = {}