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?pre_viz_calls
parent
75bb06588b
commit
04b475091c
|
@ -322,10 +322,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
|
||||||
|
@ -410,11 +406,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)
|
||||||
|
@ -443,16 +439,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
|
||||||
|
@ -465,10 +466,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
|
||||||
|
@ -548,48 +546,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][[
|
||||||
|
@ -604,10 +588,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,
|
||||||
|
@ -623,40 +604,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 (
|
if (
|
||||||
|
mx_diff
|
||||||
|
or mn_diff
|
||||||
|
):
|
||||||
|
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
|
||||||
|
@ -664,11 +669,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]
|
||||||
(
|
(
|
||||||
_,
|
_,
|
||||||
|
@ -685,7 +689,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
|
||||||
|
@ -761,10 +765,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
|
||||||
|
|
Loading…
Reference in New Issue