From 97b03bbfbb38b67436c1f4c3878ac6cebc70dbde Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 30 Nov 2022 16:10:31 -0500 Subject: [PATCH 01/15] Move old label sizing cruft to label mod --- piker/ui/_label.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/piker/ui/_label.py b/piker/ui/_label.py index 699a81ae..247b4cc0 100644 --- a/piker/ui/_label.py +++ b/piker/ui/_label.py @@ -233,6 +233,36 @@ class Label: def delete(self) -> None: self.vb.scene().removeItem(self.txt) + # NOTE: pulled out from ``ChartPlotWidget`` from way way old code. + # def _label_h(self, yhigh: float, ylow: float) -> float: + # # compute contents label "height" in view terms + # # to avoid having data "contents" overlap with them + # if self._labels: + # label = self._labels[self.name][0] + + # rect = label.itemRect() + # tl, br = rect.topLeft(), rect.bottomRight() + # vb = self.plotItem.vb + + # try: + # # on startup labels might not yet be rendered + # top, bottom = (vb.mapToView(tl).y(), vb.mapToView(br).y()) + + # # XXX: magic hack, how do we compute exactly? + # label_h = (top - bottom) * 0.42 + + # except np.linalg.LinAlgError: + # label_h = 0 + # else: + # label_h = 0 + + # # print(f'label height {self.name}: {label_h}') + + # if label_h > yhigh - ylow: + # label_h = 0 + + # print(f"bounds (ylow, yhigh): {(ylow, yhigh)}") + class FormatLabel(QLabel): ''' From b2bb7f4923844ca623a95d7f11fb051464c42459 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 14:22:44 -0500 Subject: [PATCH 02/15] Simplify L1 labels for multicharts Instead of having the l1 lines be inside the view space, move them to be inside their respective axis (with only a 16 unit portion inside the view) such that the clear price label can overlay with them nicely without obscuring; this is much better suited to multiple adjacent y-axes and in general is simpler and less noisy. Further `L1Labels` + `LevelLabel` style tweaks: - adjust `.rect` positioning to be "right" (i.e. inside the parent y-axis) with a slight 16 unit shift toward the viewbox (using the new `._x_br_offset`) to allow seeing each level label's line even when the clearing price label is positioned at that same level. - add a newline's worth of vertical space to each of the bid/ask labels so that L1 labels' text content isn't ever obscured by the clear price label. - set a low (10) z-value to ensure l1 labels are always placed underneath the clear price label. - always fill the label rect with the chosen background color. - make labels fully opaque so as to always make them hide the parent axes' `.tickStrings()` contents. - make default color the "default" from the global scheme. - drop the "price" part from the l1 label text contents, just show the book-queue's amount (in dst asset's units, aka the potential clearing vlm). --- piker/ui/_l1.py | 71 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/piker/ui/_l1.py b/piker/ui/_l1.py index 0c6e4e1a..ae3d5375 100644 --- a/piker/ui/_l1.py +++ b/piker/ui/_l1.py @@ -30,19 +30,20 @@ from ._pg_overrides import PlotItem class LevelLabel(YAxisLabel): - """Y-axis (vertically) oriented, horizontal label that sticks to + ''' + Y-axis (vertically) oriented, horizontal label that sticks to where it's placed despite chart resizing and supports displaying multiple fields. TODO: replace the rectangle-text part with our new ``Label`` type. - """ - _x_margin = 0 - _y_margin = 0 + ''' + _x_br_offset: float = -16 + _x_txt_h_scaling: float = 2 # adjustment "further away from" anchor point - _x_offset = 9 + _x_offset = 0 _y_offset = 0 # fields to be displayed in the label string @@ -58,12 +59,12 @@ class LevelLabel(YAxisLabel): chart, parent, - color: str = 'bracket', + color: str = 'default_light', orient_v: str = 'bottom', - orient_h: str = 'left', + orient_h: str = 'right', - opacity: float = 0, + opacity: float = 1, # makes order line labels offset from their parent axis # such that they don't collide with the L1/L2 lines/prices @@ -99,13 +100,15 @@ class LevelLabel(YAxisLabel): self._h_shift = { 'left': -1., - 'right': 0. + 'right': 0., }[orient_h] self.fields = self._fields.copy() # ensure default format fields are in correct self.set_fmt_str(self._fmt_str, self.fields) + self.setZValue(10) + @property def color(self): return self._hcolor @@ -113,7 +116,10 @@ class LevelLabel(YAxisLabel): @color.setter def color(self, color: str) -> None: self._hcolor = color - self._pen = self.pen = pg.mkPen(hcolor(color)) + self._pen = self.pen = pg.mkPen( + hcolor(color), + width=3, + ) def update_on_resize(self, vr, r): """Tiis is a ``.sigRangeChanged()`` handler. @@ -125,10 +131,11 @@ class LevelLabel(YAxisLabel): self, fields: dict = None, ) -> None: - """Update the label's text contents **and** position from + ''' + Update the label's text contents **and** position from a view box coordinate datum. - """ + ''' self.fields.update(fields) level = self.fields['level'] @@ -175,7 +182,8 @@ class LevelLabel(YAxisLabel): fields: dict, ): # use space as e3 delim - self.label_str = self._fmt_str.format(**fields).replace(',', ' ') + self.label_str = self._fmt_str.format( + **fields).replace(',', ' ') br = self.boundingRect() h, w = br.height(), br.width() @@ -188,14 +196,14 @@ class LevelLabel(YAxisLabel): self, p: QtGui.QPainter, rect: QtCore.QRectF - ) -> None: - p.setPen(self._pen) + ) -> None: + + p.setPen(self._pen) rect = self.rect if self._orient_v == 'bottom': lp, rp = rect.topLeft(), rect.topRight() - # p.drawLine(rect.topLeft(), rect.topRight()) elif self._orient_v == 'top': lp, rp = rect.bottomLeft(), rect.bottomRight() @@ -209,6 +217,11 @@ class LevelLabel(YAxisLabel): ]) ) + p.fillRect( + self.rect, + self.bg_color, + ) + def highlight(self, pen) -> None: self._pen = pen self.update() @@ -247,9 +260,10 @@ class L1Label(LevelLabel): class L1Labels: - """Level 1 bid ask labels for dynamic update on price-axis. + ''' + Level 1 bid ask labels for dynamic update on price-axis. - """ + ''' def __init__( self, plotitem: PlotItem, @@ -265,15 +279,17 @@ class L1Labels: 'chart': plotitem, 'parent': raxis, - 'opacity': 1, + 'opacity': .9, 'font_size': font_size, - 'fg_color': chart.pen_color, - 'bg_color': chart.view_color, + 'fg_color': 'default_light', + 'bg_color': chart.view_color, # normally 'papas_special' } + # TODO: add humanized source-asset + # info format. fmt_str = ( - ' {size:.{size_digits}f} x ' - '{level:,.{level_digits}f} ' + ' {size:.{size_digits}f} u' + # '{level:,.{level_digits}f} ' ) fields = { 'level': 0, @@ -286,12 +302,17 @@ class L1Labels: orient_v='bottom', **kwargs, ) - bid.set_fmt_str(fmt_str=fmt_str, fields=fields) + bid.set_fmt_str( + fmt_str='\n' + fmt_str, + fields=fields, + ) bid.show() ask = self.ask_label = L1Label( orient_v='top', **kwargs, ) - ask.set_fmt_str(fmt_str=fmt_str, fields=fields) + ask.set_fmt_str( + fmt_str=fmt_str, + fields=fields) ask.show() From e37e118a7e1843858a53913ceee80d874c761e66 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 14:44:14 -0500 Subject: [PATCH 03/15] Don't set y-axis label colors to curve's, use the default from global scheme --- piker/ui/_chart.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 14921b3d..67120f5e 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -1231,7 +1231,8 @@ class ChartPlotWidget(pg.PlotWidget): # (we need something that avoids clutter on x-axis). axis.add_sticky( pi=pi, - bg_color=color, + fg_color='black', + # bg_color=color, digits=digits, ) From 26f497e2bb845e0159157671fd367e7b89e674cc Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 14:45:02 -0500 Subject: [PATCH 04/15] Set cursor label color to "bracket" --- piker/ui/_cursor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/piker/ui/_cursor.py b/piker/ui/_cursor.py index 762acf73..3172be1a 100644 --- a/piker/ui/_cursor.py +++ b/piker/ui/_cursor.py @@ -71,7 +71,7 @@ class LineDot(pg.CurvePoint): plot: ChartPlotWidget, # type: ingore # noqa pos=None, - color: str = 'default_light', + color: str = 'bracket', ) -> None: # scale from dpi aware font size @@ -349,7 +349,7 @@ class Cursor(pg.GraphicsObject): # XXX: not sure why these are instance variables? # It's not like we can change them on the fly..? self.pen = pg.mkPen( - color=hcolor('default'), + color=hcolor('bracket'), style=QtCore.Qt.DashLine, ) self.lines_pen = pg.mkPen( @@ -365,7 +365,7 @@ class Cursor(pg.GraphicsObject): self._lw = self.pixelWidth() * self.lines_pen.width() # xhair label's color name - self.label_color: str = 'default' + self.label_color: str = 'bracket' self._y_label_update: bool = True From 924fcca4638fe7928e44233278c4a6665edef9ec Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 15:12:02 -0500 Subject: [PATCH 05/15] TOSQUASH: 84f19308 (l1 rework) --- piker/ui/_l1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piker/ui/_l1.py b/piker/ui/_l1.py index ae3d5375..23162c70 100644 --- a/piker/ui/_l1.py +++ b/piker/ui/_l1.py @@ -40,7 +40,7 @@ class LevelLabel(YAxisLabel): ''' _x_br_offset: float = -16 - _x_txt_h_scaling: float = 2 + _y_txt_h_scaling: float = 2 # adjustment "further away from" anchor point _x_offset = 0 From 1d83fdb510f5378ed5818cdaf34dda38455b4fdb Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 15:45:57 -0500 Subject: [PATCH 06/15] Handle empty `indexes` input edge case.. --- piker/ui/_axes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/piker/ui/_axes.py b/piker/ui/_axes.py index 4d197f5a..b6fb9281 100644 --- a/piker/ui/_axes.py +++ b/piker/ui/_axes.py @@ -306,7 +306,10 @@ class DynamicDateAxis(Axis): times = array['time'] i_0, i_l = times[0], times[-1] + # edge cases if ( + not indexes + or (indexes[0] < i_0 and indexes[-1] < i_l) or From a7a08aced99165fa677e7541fad71028a5261ccd Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 23 Dec 2022 16:23:42 -0500 Subject: [PATCH 07/15] Drop l1 labels attr from chart widget --- piker/ui/_chart.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 67120f5e..0f8c33c4 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -829,8 +829,6 @@ class ChartPlotWidget(pg.PlotWidget): sig_mouse_leave = QtCore.pyqtSignal(object) sig_mouse_enter = QtCore.pyqtSignal(object) - _l1_labels: L1Labels = None - mode_name: str = 'view' # TODO: can take a ``background`` color setting - maybe there's From 3fd394d6937f1bccb88c790e342197419b74201a Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 27 Dec 2022 13:10:25 -0500 Subject: [PATCH 08/15] Use static `L1Label._x_br_offset` as l1 label length --- piker/ui/_chart.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 0f8c33c4..ed278acc 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -984,7 +984,8 @@ class ChartPlotWidget(pg.PlotWidget): ''' # TODO: compute some sensible maximum value here # and use a humanized scheme to limit to that length. - l1_len = self._max_l1_line_len + from ._l1 import L1Label + l1_len = abs(L1Label._x_br_offset) ryaxis = self.getAxis('right') r_axis_x = ryaxis.pos().x() From 42d3537516c74de46763248ca74065caefa076bf Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 14:59:44 -0500 Subject: [PATCH 09/15] 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? --- piker/ui/_display.py | 128 ++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/piker/ui/_display.py b/piker/ui/_display.py index e544b64d..aa0a2e1b 100644 --- a/piker/ui/_display.py +++ b/piker/ui/_display.py @@ -328,10 +328,6 @@ async def graphics_update_loop( digits=symbol.tick_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: # - 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() - for sym, quote in quotes.items(): - ds = dss[sym] + for fqsn, quote in quotes.items(): + ds = dss[fqsn] 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 # pause feed(s) @@ -449,16 +445,21 @@ async def graphics_update_loop( def graphics_update_cycle( ds: DisplayState, quote: dict, + wap_in_history: bool = False, trigger_all: bool = False, # flag used by prepend history updates prepend_update_index: Optional[int] = 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 - # TODO: just pass this as a direct ref to avoid so many attr accesses? hist_chart = ds.godwidget.hist_linked.chart flume = ds.flume @@ -471,10 +472,7 @@ def graphics_update_cycle( msg=f'Graphics loop cycle for: `{chart.name}`', delayed=True, disabled=not pg_profile_enabled(), - # disabled=True, ms_threshold=ms_slower_then, - - # ms_threshold=1/12 * 1e3, ) # unpack multi-referenced components @@ -554,48 +552,34 @@ def graphics_update_cycle( profiler('view incremented') - # from pprint import pformat - # frame_counts = { - # 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', ()) + # iterate frames of ticks-by-type such that we only update graphics + # using the last update per type where possible. ticks_by_type = quote.get('tbt', {}) - - # for tick in ticks_frame: for typ, ticks in ticks_by_type.items(): # NOTE: ticks are `.append()`-ed to the `ticks_by_type: dict` by the # `._sampling.uniform_rate_send()` loop - tick = ticks[-1] - # typ = tick.get('type') + tick = ticks[-1] # get most recent value + price = tick.get('price') size = tick.get('size') # compute max and min prices (including bid/ask) from # tick frames to determine the y-range for chart # auto-scaling. - # TODO: we need a streaming minmax algo here, see def above. - if liv: + if ( + liv + + # TODO: make sure IB doesn't send ``-1``! + and price > 0 + ): mx = max(price + tick_margin, mx) 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: - # 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) end_ic = array[-1][[ @@ -610,10 +594,7 @@ def graphics_update_cycle( chart.update_graphics_from_flow('bar_wap') # L1 book label-line updates - # XXX: is this correct for ib? - # if ticktype in ('trade', 'last'): - # if ticktype in ('last',): # 'size'): - if typ in ('last',): # 'size'): + if typ in ('last',): 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 - # the relevant L1 queue? + # the relevant L1 queue manually ourselves? # 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 ( 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}) elif ( 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}) - # check for y-range re-size - if (mx > varz['last_mx']) or (mn < varz['last_mn']): + # check for y-autorange re-size + 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 ( + 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 and not chart._static_yrange == 'axis' ): - # main_vb = chart.view main_vb = chart._vizs[fqsn].plot.vb if ( main_vb._ic is None 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( # TODO: we should probably scale # the view margin based on the size @@ -670,11 +675,10 @@ def graphics_update_cycle( # slap in orders outside the current # L1 (only) book range. # range_margin=0.1, - # yrange=(mn, mx), + yrange=yr ) # check if slow chart needs a resize - hist_viz = hist_chart._vizs[fqsn] ( _, @@ -691,7 +695,7 @@ def graphics_update_cycle( if hist_liv: 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 # run synchronous update on all linked viz @@ -767,10 +771,8 @@ def graphics_update_cycle( if ( mx_vlm_in_view != varz['last_mx_vlm'] ): - yrange = (0, mx_vlm_in_view * 1.375) - vlm_chart.view._set_yrange( - yrange=yrange, - ) + vlm_yr = (0, mx_vlm_in_view * 1.375) + vlm_chart.view._set_yrange(yrange=vlm_yr) profiler('`vlm_chart.view._set_yrange()`') # print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}') varz['last_mx_vlm'] = mx_vlm_in_view From 4c51a686917fd82fbbad65b1134aa0e512a973fb Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 15:06:07 -0500 Subject: [PATCH 10/15] Better index step value scanning by checking with our expected set --- piker/ui/_dataviz.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/piker/ui/_dataviz.py b/piker/ui/_dataviz.py index 07ead769..d43377b9 100644 --- a/piker/ui/_dataviz.py +++ b/piker/ui/_dataviz.py @@ -217,6 +217,9 @@ def render_baritems( ) +_sample_rates: set[float] = {1, 60} + + class Viz(msgspec.Struct): # , frozen=True): ''' (Data) "Visualization" compound type which wraps a real-time @@ -284,15 +287,33 @@ class Viz(msgspec.Struct): # , frozen=True): reset: bool = False, ) -> float: + + # attempt to dectect the best step size by scanning a sample of + # the source data. if self._index_step is None: + index = self.shm.array[self.index_field] isample = index[:16] - mxdiff = np.diff(isample).max() + + mxdiff: None | float = None + for step in np.diff(isample): + if step in _sample_rates: + if ( + mxdiff is not None + and step != mxdiff + ): + raise ValueError( + f'Multiple step sizes detected? {mxdiff}, {step}' + ) + mxdiff = step + self._index_step = max(mxdiff, 1) if ( mxdiff < 1 or 1 < mxdiff < 60 ): + # TODO: remove this once we're sure the above scan loop + # is rock solid. breakpoint() return self._index_step From dc883642531fefee071b526c047dd659ade3bc86 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 15:32:02 -0500 Subject: [PATCH 11/15] Move $vlm y-axis to LHS --- piker/ui/_fsp.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/piker/ui/_fsp.py b/piker/ui/_fsp.py index 5dee7b88..98ddd502 100644 --- a/piker/ui/_fsp.py +++ b/piker/ui/_fsp.py @@ -755,7 +755,7 @@ async def open_vlm_displays( { # fsp engine conf 'func_name': 'dolla_vlm', - 'zero_on_step': True, + 'zero_on_step': False, 'params': { 'price_func': { 'default_value': 'chl3', @@ -769,7 +769,7 @@ async def open_vlm_displays( # FIXME: we should error on starting the same fsp right # since it might collide with existing shm.. or wait we # had this before?? - # dolla_vlm, + # dolla_vlm tasks_ready.append(started) # profiler(f'created shm for fsp actor: {display_name}') @@ -786,8 +786,10 @@ async def open_vlm_displays( dvlm_pi = vlm_chart.overlay_plotitem( 'dolla_vlm', index=0, # place axis on inside (nearest to chart) + axis_title=' $vlm', - axis_side='right', + axis_side='left', + axis_kwargs={ 'typical_max_str': ' 100.0 M ', 'formatter': partial( @@ -797,8 +799,10 @@ async def open_vlm_displays( }, ) - dvlm_pi.hideAxis('left') + # TODO: should this maybe be implicit based on input args to + # `.overlay_plotitem()` above? dvlm_pi.hideAxis('bottom') + # all to be overlayed curve names fields = [ 'dolla_vlm', @@ -879,7 +883,7 @@ async def open_vlm_displays( flow_rates, { # fsp engine conf 'func_name': 'flow_rates', - 'zero_on_step': False, + 'zero_on_step': True, }, # loglevel, ) @@ -913,8 +917,8 @@ async def open_vlm_displays( # TODO: dynamically update period (and thus this axis?) # title from user input. axis_title='clears', - axis_side='left', + axis_kwargs={ 'typical_max_str': ' 10.0 M ', 'formatter': partial( From a82911d8a95f82d9fabdf6b96b3ab1218c3111e8 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 16:21:23 -0500 Subject: [PATCH 12/15] Correctly load order mode for first fqsn in overlay set --- piker/ui/_display.py | 71 +++++++++++++++++------------------------- piker/ui/order_mode.py | 5 ++- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/piker/ui/_display.py b/piker/ui/_display.py index aa0a2e1b..482cdfb6 100644 --- a/piker/ui/_display.py +++ b/piker/ui/_display.py @@ -1049,6 +1049,10 @@ async def display_symbol_data( group_key=True ) + # (TODO: make this not so shit XD) + # close group status once a symbol feed fully loads to view. + # sbar._status_groups[loading_sym_key][1]() + # TODO: ctl over update loop's maximum frequency. # - load this from a config.toml! # - allow dyanmic configuration from chart UI? @@ -1131,6 +1135,8 @@ async def display_symbol_data( # and sub-charts for FSPs fqsn, flume = fitems[0] + # TODO NOTE: THIS CONTROLS WHAT SYMBOL IS USED FOR ORDER MODE + # SUBMISSIONS, we need to make this switch based on selection. rt_linked._symbol = flume.symbol hist_linked._symbol = flume.symbol @@ -1221,9 +1227,6 @@ async def display_symbol_data( # get a new color from the palette bg_chart_color, bg_last_bar_color = next(palette) - rt_linked._symbol = flume.symbol - hist_linked._symbol = flume.symbol - ohlcv: ShmArray = flume.rt_shm hist_ohlcv: ShmArray = flume.hist_shm @@ -1234,9 +1237,14 @@ async def display_symbol_data( name=fqsn, axis_title=fqsn, ) - hist_pi.hideAxis('left') + # only show a singleton bottom-bottom axis by default. hist_pi.hideAxis('bottom') + # XXX: TODO: THIS WILL CAUSE A GAP ON OVERLAYS, + # i think it needs to be "removed" instead when there + # are none? + hist_pi.hideAxis('left') + viz = hist_chart.draw_curve( fqsn, hist_ohlcv, @@ -1313,36 +1321,14 @@ async def display_symbol_data( # XXX: if we wanted it at the bottom? # rt_linked.splitter.addWidget(hist_linked) - rt_linked.focus() - godwidget.resize_all() - - # add all additional symbols as overlays + # greedily do a view range default and pane resizing + # on startup before loading the order-mode machinery. for fqsn, flume in feed.flumes.items(): # size view to data prior to order mode init rt_chart.default_view() rt_linked.graphics_cycle() - await trio.sleep(0) - - hist_chart.default_view( - bars_from_y=int(len(hist_ohlcv.array)), # size to data - y_offset=6116*2, # push it a little away from the y-axis - ) - hist_linked.graphics_cycle() - await trio.sleep(0) - - godwidget.resize_all() - - # trigger another view reset if no sub-chart - hist_chart.default_view() - rt_chart.default_view() - # let qt run to render all widgets and make sure the - # sidepanes line up vertically. - await trio.sleep(0) - - # dynamic resize steps - godwidget.resize_all() # TODO: look into this because not sure why it was # commented out / we ever needed it XD @@ -1352,21 +1338,11 @@ async def display_symbol_data( # determine if auto-range adjustements should be made. # rt_linked.subplots.pop('volume', None) - # TODO: make this not so shit XD - # close group status - # sbar._status_groups[loading_sym_key][1]() - + hist_chart.default_view() hist_linked.graphics_cycle() - rt_chart.default_view() - await trio.sleep(0) - bars_in_mem = int(len(hist_ohlcv.array)) - hist_chart.default_view( - bars_from_y=bars_in_mem, # size to data - # push it 1/16th away from the y-axis - y_offset=round(bars_in_mem / 16), - ) godwidget.resize_all() + await trio.sleep(0) await link_views_with_region( rt_chart, @@ -1374,7 +1350,7 @@ async def display_symbol_data( flume, ) - # start graphics update loop after receiving first live quote + # start update loop task ln.start_soon( graphics_update_loop, ln, @@ -1385,20 +1361,31 @@ async def display_symbol_data( vlm_charts, ) + # boot order-mode + order_ctl_symbol: str = fqsns[0] mode: OrderMode async with ( open_order_mode( feed, godwidget, - fqsns[-1], + fqsns[0], order_mode_started ) as mode ): + rt_linked.mode = mode + viz = rt_chart.get_viz(order_ctl_symbol) + viz.plot.setFocus() + + # default view adjuments and sidepane alignment + # as final default UX touch. rt_chart.default_view() rt_chart.view.enable_auto_yrange() + hist_chart.default_view() hist_chart.view.enable_auto_yrange() + godwidget.resize_all() + await trio.sleep_forever() # let the app run.. bby diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 4ac2f457..e339739e 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -349,7 +349,7 @@ class OrderMode: ''' if not order: - staged = self._staged_order + staged: Order = self._staged_order # apply order fields for ems oid = str(uuid.uuid4()) order = staged.copy() @@ -703,7 +703,6 @@ async def open_order_mode( # symbol id symbol = chart.linked.symbol - symkey = symbol.front_fqsn() # map of per-provider account keys to position tracker instances trackers: dict[str, PositionTracker] = {} @@ -864,7 +863,7 @@ async def open_order_mode( # the expected symbol key in its positions msg. for (broker, acctid), msgs in position_msgs.items(): for msg in msgs: - log.info(f'Loading pp for {symkey}:\n{pformat(msg)}') + log.info(f'Loading pp for {acctid}@{broker}:\n{pformat(msg)}') await process_trade_msg( mode, book, From 5bd6fa3cbf67150ac26b4caadbac4f875b8f8ff1 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 17:13:44 -0500 Subject: [PATCH 13/15] Make $vlm axis color same as clears --- piker/ui/_fsp.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/piker/ui/_fsp.py b/piker/ui/_fsp.py index 98ddd502..c91436da 100644 --- a/piker/ui/_fsp.py +++ b/piker/ui/_fsp.py @@ -661,6 +661,12 @@ async def open_vlm_displays( # str(period_param.default) # ) + # use slightly less light (then bracket) gray + # for volume from "main exchange" and a more "bluey" + # gray for "dark" vlm. + vlm_color = 'i3' + dark_vlm_color = 'charcoal' + # built-in vlm which we plot ASAP since it's # usually data provided directly with OHLC history. shm = ohlcv @@ -796,6 +802,7 @@ async def open_vlm_displays( humanize, digits=2, ), + 'text_color': vlm_color, }, ) @@ -827,12 +834,6 @@ async def open_vlm_displays( # add custom auto range handler dvlm_pi.vb._maxmin = group_mxmn - # use slightly less light (then bracket) gray - # for volume from "main exchange" and a more "bluey" - # gray for "dark" vlm. - vlm_color = 'i3' - dark_vlm_color = 'charcoal' - # add dvlm (step) curves to common view def chart_curves( names: list[str], From 1abed2ad9e7362177d2552eb46343384ec182ea4 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 21:08:51 -0500 Subject: [PATCH 14/15] Fix indent level --- piker/ui/_overlay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/piker/ui/_overlay.py b/piker/ui/_overlay.py index ac15a9dc..0fb747e7 100644 --- a/piker/ui/_overlay.py +++ b/piker/ui/_overlay.py @@ -361,8 +361,8 @@ class PlotItemOverlay: if not sub_handlers: src_handler = getattr( - root.vb, - ev_name, + root.vb, + ev_name, ) def broadcast( From cee6321a9f02d59786e49c8b638efedba462e5d3 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 2 Jan 2023 21:11:36 -0500 Subject: [PATCH 15/15] Do full marker width after line --- piker/ui/_chart.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index ed278acc..7f078a47 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -991,7 +991,8 @@ class ChartPlotWidget(pg.PlotWidget): r_axis_x = ryaxis.pos().x() up_to_l1_sc = r_axis_x - l1_len marker_right = up_to_l1_sc - (1.375 * 2 * marker_size) - line_end = marker_right - (6/16 * marker_size) + # line_end = marker_right - (6/16 * marker_size) + line_end = marker_right - marker_size # print( # f'r_axis_x: {r_axis_x}\n'