Repair auto-y-ranging to always include L1 spread

Goes back to always adjusting the y-axis range to include the L1 spread
and clearing label in view whenever the last datum is also in view,
previously this was broken after reworking the display loop for
multi-feeds.

Drops a bunch of old commented tick looping cruft from before we started
using tick-type framing. Also adds more stringent guards for ignoring
but error logging quote values that are more then 25% out of range; it
seems particularly our `ib` feed has some issues with strange `price`
values that are way off here and there?
l1_compaction
Tyler Goodlet 2023-01-02 14:59:44 -05:00
parent 3fd394d693
commit 42d3537516
1 changed files with 65 additions and 63 deletions

View File

@ -328,10 +328,6 @@ async def graphics_update_loop(
digits=symbol.tick_size_digits, digits=symbol.tick_size_digits,
size_digits=symbol.lot_size_digits, size_digits=symbol.lot_size_digits,
) )
# TODO: this is just wrong now since we can have multiple L1-label
# sets, so instead we should have the l1 associated with the
# plotitem or y-axis likely?
# fast_chart._l1_labels = l1
# TODO: # TODO:
# - in theory we should be able to read buffer data faster # - in theory we should be able to read buffer data faster
@ -416,11 +412,11 @@ async def graphics_update_loop(
last_quote_s = time.time() last_quote_s = time.time()
for sym, quote in quotes.items(): for fqsn, quote in quotes.items():
ds = dss[sym] ds = dss[fqsn]
ds.quotes = quote ds.quotes = quote
rt_pi, hist_pi = pis[sym] rt_pi, hist_pi = pis[fqsn]
# chart isn't active/shown so skip render cycle and # chart isn't active/shown so skip render cycle and
# pause feed(s) # pause feed(s)
@ -449,16 +445,21 @@ async def graphics_update_loop(
def graphics_update_cycle( def graphics_update_cycle(
ds: DisplayState, ds: DisplayState,
quote: dict, quote: dict,
wap_in_history: bool = False, wap_in_history: bool = False,
trigger_all: bool = False, # flag used by prepend history updates trigger_all: bool = False, # flag used by prepend history updates
prepend_update_index: Optional[int] = None, prepend_update_index: Optional[int] = None,
) -> None: ) -> None:
# TODO: eventually optimize this whole graphics stack with ``numba``
# hopefully XD # TODO: SPEEDing this all up..
# - optimize this whole graphics stack with ``numba`` hopefully
# 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?
chart = ds.chart chart = ds.chart
# TODO: just pass this as a direct ref to avoid so many attr accesses?
hist_chart = ds.godwidget.hist_linked.chart hist_chart = ds.godwidget.hist_linked.chart
flume = ds.flume flume = ds.flume
@ -471,10 +472,7 @@ def graphics_update_cycle(
msg=f'Graphics loop cycle for: `{chart.name}`', msg=f'Graphics loop cycle for: `{chart.name}`',
delayed=True, delayed=True,
disabled=not pg_profile_enabled(), disabled=not pg_profile_enabled(),
# disabled=True,
ms_threshold=ms_slower_then, ms_threshold=ms_slower_then,
# ms_threshold=1/12 * 1e3,
) )
# unpack multi-referenced components # unpack multi-referenced components
@ -554,48 +552,34 @@ def graphics_update_cycle(
profiler('view incremented') profiler('view incremented')
# from pprint import pformat # iterate frames of ticks-by-type such that we only update graphics
# frame_counts = { # using the last update per type where possible.
# typ: len(frame) for typ, frame in frames_by_type.items()
# }
# print(
# f'{pformat(frame_counts)}\n'
# f'framed: {pformat(frames_by_type)}\n'
# f'lasts: {pformat(lasts)}\n'
# )
# for typ, tick in lasts.items():
# ticks_frame = quote.get('ticks', ())
ticks_by_type = quote.get('tbt', {}) ticks_by_type = quote.get('tbt', {})
# for tick in ticks_frame:
for typ, ticks in ticks_by_type.items(): for typ, ticks in ticks_by_type.items():
# NOTE: ticks are `.append()`-ed to the `ticks_by_type: dict` by the # NOTE: ticks are `.append()`-ed to the `ticks_by_type: dict` by the
# `._sampling.uniform_rate_send()` loop # `._sampling.uniform_rate_send()` loop
tick = ticks[-1] tick = ticks[-1] # get most recent value
# typ = tick.get('type')
price = tick.get('price') price = tick.get('price')
size = tick.get('size') size = tick.get('size')
# compute max and min prices (including bid/ask) from # compute max and min prices (including bid/ask) from
# tick frames to determine the y-range for chart # tick frames to determine the y-range for chart
# auto-scaling. # auto-scaling.
# TODO: we need a streaming minmax algo here, see def above. if (
if liv: liv
# TODO: make sure IB doesn't send ``-1``!
and price > 0
):
mx = max(price + tick_margin, mx) mx = max(price + tick_margin, mx)
mn = min(price - tick_margin, mn) mn = min(price - tick_margin, mn)
# clearing price update:
# generally, we only want to update grahpics from the *last*
# tick event once - thus showing the most recent state.
if typ in clear_types: if typ in clear_types:
# XXX: if we only wanted to update graphics from the
# "current"/"latest received" clearing price tick
# once (see alt iteration order above).
# if last_clear_updated:
# continue
# last_clear_updated = True
# we only want to update grahpics from the *last*
# tick event that falls under the "clearing price"
# set.
# update price sticky(s) # update price sticky(s)
end_ic = array[-1][[ end_ic = array[-1][[
@ -610,10 +594,7 @@ def graphics_update_cycle(
chart.update_graphics_from_flow('bar_wap') chart.update_graphics_from_flow('bar_wap')
# L1 book label-line updates # L1 book label-line updates
# XXX: is this correct for ib? if typ in ('last',):
# if ticktype in ('trade', 'last'):
# if ticktype in ('last',): # 'size'):
if typ in ('last',): # 'size'):
label = { label = {
l1.ask_label.fields['level']: l1.ask_label, l1.ask_label.fields['level']: l1.ask_label,
@ -629,40 +610,64 @@ def graphics_update_cycle(
) )
# TODO: on trades should we be knocking down # TODO: on trades should we be knocking down
# the relevant L1 queue? # the relevant L1 queue manually ourselves?
# label.size -= size # label.size -= size
# NOTE: right now we always update the y-axis labels
# despite the last datum not being in view. Ideally
# we have a guard for this when we detect that the range
# of those values is not in view and then we disable these
# blocks.
elif ( elif (
typ in _tick_groups['asks'] typ in _tick_groups['asks']
# TODO: instead we could check if the price is in the
# y-view-range?
and liv
): ):
l1.ask_label.update_fields({'level': price, 'size': size}) l1.ask_label.update_fields({'level': price, 'size': size})
elif ( elif (
typ in _tick_groups['bids'] typ in _tick_groups['bids']
# TODO: instead we could check if the price is in the
# y-view-range?
and liv
): ):
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-autorange re-size
if (mx > varz['last_mx']) or (mn < varz['last_mn']): lmx = varz['last_mx']
lmn = varz['last_mn']
mx_diff = mx - lmx
mn_diff = mn - lmn
# fast chart resize case if (
mx_diff
or mn_diff
):
if ( if (
abs(mx_diff) > .25 * lmx
or
abs(mn_diff) > .25 * lmn
):
log.error(
f'WTF MN/MX IS WAY OFF:\n'
f'lmn: {lmn}\n'
f'mn: {mn}\n'
f'lmx: {lmx}\n'
f'mx: {mx}\n'
f'mx_diff: {mx_diff}\n'
f'mn_diff: {mn_diff}\n'
)
# fast chart resize case
elif (
liv liv
and not chart._static_yrange == 'axis' and not chart._static_yrange == 'axis'
): ):
# main_vb = chart.view
main_vb = chart._vizs[fqsn].plot.vb main_vb = chart._vizs[fqsn].plot.vb
if ( if (
main_vb._ic is None main_vb._ic is None
or not main_vb._ic.is_set() or not main_vb._ic.is_set()
): ):
# print(f'updating range due to mxmn') yr = (mn, mx)
# print(
# f'updating y-range due to mxmn\n'
# f'{fqsn}: {yr}'
# )
main_vb._set_yrange( main_vb._set_yrange(
# TODO: we should probably scale # TODO: we should probably scale
# the view margin based on the size # the view margin based on the size
@ -670,11 +675,10 @@ def graphics_update_cycle(
# slap in orders outside the current # slap in orders outside the current
# L1 (only) book range. # L1 (only) book range.
# range_margin=0.1, # range_margin=0.1,
# yrange=(mn, mx), yrange=yr
) )
# check if slow chart needs a resize # check if slow chart needs a resize
hist_viz = hist_chart._vizs[fqsn] hist_viz = hist_chart._vizs[fqsn]
( (
_, _,
@ -691,7 +695,7 @@ def graphics_update_cycle(
if hist_liv: if hist_liv:
hist_viz.plot.vb._set_yrange() hist_viz.plot.vb._set_yrange()
# XXX: update this every draw cycle to make L1-always-in-view work. # XXX: update this every draw cycle to make
varz['last_mx'], varz['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
@ -767,10 +771,8 @@ def graphics_update_cycle(
if ( if (
mx_vlm_in_view != varz['last_mx_vlm'] mx_vlm_in_view != varz['last_mx_vlm']
): ):
yrange = (0, mx_vlm_in_view * 1.375) vlm_yr = (0, mx_vlm_in_view * 1.375)
vlm_chart.view._set_yrange( vlm_chart.view._set_yrange(yrange=vlm_yr)
yrange=yrange,
)
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}')
varz['last_mx_vlm'] = mx_vlm_in_view varz['last_mx_vlm'] = mx_vlm_in_view