Add display loop profiling
Probably the best place to root the profiler since we can get a better top down view of bottlenecks in the graphics stack. More, - add in draft M4 downsampling code (commented) after getting it mostly working; next step is to move this processing into an FSP subactor. - always update the vlm chart last y-axis sticky - set call `.default_view()` just before inf sleep on startupmkts_backup
							parent
							
								
									a4dd6c81dc
								
							
						
					
					
						commit
						23a368b5e5
					
				| 
						 | 
					@ -29,6 +29,8 @@ from typing import Optional, Any, Callable
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
import tractor
 | 
					import tractor
 | 
				
			||||||
import trio
 | 
					import trio
 | 
				
			||||||
 | 
					import pyqtgraph as pg
 | 
				
			||||||
 | 
					from PyQt5.QtCore import QLineF, QPointF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .. import brokers
 | 
					from .. import brokers
 | 
				
			||||||
from ..data.feed import open_feed
 | 
					from ..data.feed import open_feed
 | 
				
			||||||
| 
						 | 
					@ -178,7 +180,6 @@ async def graphics_update_loop(
 | 
				
			||||||
        vlm_sticky = vlm_chart._ysticks['volume']
 | 
					        vlm_sticky = vlm_chart._ysticks['volume']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    maxmin = partial(chart_maxmin, chart, vlm_chart)
 | 
					    maxmin = partial(chart_maxmin, chart, vlm_chart)
 | 
				
			||||||
    chart.default_view()
 | 
					 | 
				
			||||||
    last_bars_range: tuple[float, float]
 | 
					    last_bars_range: tuple[float, float]
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
        last_bars_range,
 | 
					        last_bars_range,
 | 
				
			||||||
| 
						 | 
					@ -258,6 +259,8 @@ async def graphics_update_loop(
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chart.default_view()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # main loop
 | 
					    # main loop
 | 
				
			||||||
    async for quotes in stream:
 | 
					    async for quotes in stream:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -296,6 +299,10 @@ def graphics_update_cycle(
 | 
				
			||||||
    # TODO: eventually optimize this whole graphics stack with ``numba``
 | 
					    # TODO: eventually optimize this whole graphics stack with ``numba``
 | 
				
			||||||
    # hopefully XD
 | 
					    # hopefully XD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profiler = pg.debug.Profiler(
 | 
				
			||||||
 | 
					        disabled=False,  # not pg_profile_enabled(),
 | 
				
			||||||
 | 
					        delayed=False,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # unpack multi-referenced components
 | 
					    # unpack multi-referenced components
 | 
				
			||||||
    chart = ds.chart
 | 
					    chart = ds.chart
 | 
				
			||||||
    vlm_chart = ds.vlm_chart
 | 
					    vlm_chart = ds.vlm_chart
 | 
				
			||||||
| 
						 | 
					@ -306,11 +313,68 @@ def graphics_update_cycle(
 | 
				
			||||||
    vars = ds.vars
 | 
					    vars = ds.vars
 | 
				
			||||||
    tick_margin = vars['tick_margin']
 | 
					    tick_margin = vars['tick_margin']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    update_uppx = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for sym, quote in ds.quotes.items():
 | 
					    for sym, quote in ds.quotes.items():
 | 
				
			||||||
        brange, mx_in_view, mn_in_view, mx_vlm_in_view = ds.maxmin()
 | 
					        brange, mx_in_view, mn_in_view, mx_vlm_in_view = ds.maxmin()
 | 
				
			||||||
        l, lbar, rbar, r = brange
 | 
					        l, lbar, rbar, r = brange
 | 
				
			||||||
        mx = mx_in_view + tick_margin
 | 
					        mx = mx_in_view + tick_margin
 | 
				
			||||||
        mn = mn_in_view - tick_margin
 | 
					        mn = mn_in_view - tick_margin
 | 
				
			||||||
 | 
					        profiler('maxmin call')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # compute the first available graphic's x-units-per-pixel
 | 
				
			||||||
 | 
					        xpx = vlm_chart.view.xs_in_px()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        in_view = chart.in_view(ohlcv.array)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if lbar != rbar:
 | 
				
			||||||
 | 
					            # view box width in pxs
 | 
				
			||||||
 | 
					            w = chart.view.boundingRect().width()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # TODO: a better way to get this?
 | 
				
			||||||
 | 
					            # i would guess the esiest way is to just
 | 
				
			||||||
 | 
					            # get the ``.boundingRect()`` of the curve
 | 
				
			||||||
 | 
					            # in view but maybe there's something smarter?
 | 
				
			||||||
 | 
					            # Currently we're just mapping the rbar, lbar to
 | 
				
			||||||
 | 
					            # pixels via:
 | 
				
			||||||
 | 
					            cw = chart.view.mapViewToDevice(QLineF(lbar, 0, rbar, 0)).length()
 | 
				
			||||||
 | 
					            # is this faster?
 | 
				
			||||||
 | 
					            # cw = chart.mapFromView(QLineF(lbar, 0 , rbar, 0)).length()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            profiler(
 | 
				
			||||||
 | 
					                f'view width pxs: {w}\n'
 | 
				
			||||||
 | 
					                f'curve width pxs: {cw}\n'
 | 
				
			||||||
 | 
					                f'sliced in view: {in_view.size}'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # compress bars to m4 line(s) if uppx is high enough
 | 
				
			||||||
 | 
					            # if in_view.size > cw:
 | 
				
			||||||
 | 
					            #     from ._compression import ds_m4, hl2mxmn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            #     mxmn, x = hl2mxmn(in_view)
 | 
				
			||||||
 | 
					            #     profiler('hl tracer')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            #     nb, x, y = ds_m4(
 | 
				
			||||||
 | 
					            #         x=x,
 | 
				
			||||||
 | 
					            #         y=mxmn,
 | 
				
			||||||
 | 
					            #         # TODO: this needs to actually be the width
 | 
				
			||||||
 | 
					            #         # in pixels of the visible curve since we don't
 | 
				
			||||||
 | 
					            #         # want to downsample any 'zeros' around the curve,
 | 
				
			||||||
 | 
					            #         # just the values that make up the curve graphic,
 | 
				
			||||||
 | 
					            #         # i think?
 | 
				
			||||||
 | 
					            #         px_width=cw,
 | 
				
			||||||
 | 
					            #     )
 | 
				
			||||||
 | 
					            #     profiler(
 | 
				
			||||||
 | 
					            #         'm4 downsampled\n'
 | 
				
			||||||
 | 
					            #         f' ds bins: {nb}\n'
 | 
				
			||||||
 | 
					            #         f' x.shape: {x.shape}\n'
 | 
				
			||||||
 | 
					            #         f' y.shape: {y.shape}\n'
 | 
				
			||||||
 | 
					            #         f' x: {x}\n'
 | 
				
			||||||
 | 
					            #         f' y: {y}\n'
 | 
				
			||||||
 | 
					            #     )
 | 
				
			||||||
 | 
					                # breakpoint()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # assert y.size == mxmn.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # NOTE: vlm may be written by the ``brokerd`` backend
 | 
					        # NOTE: vlm may be written by the ``brokerd`` backend
 | 
				
			||||||
        # event though a tick sample is not emitted.
 | 
					        # event though a tick sample is not emitted.
 | 
				
			||||||
| 
						 | 
					@ -324,10 +388,6 @@ def graphics_update_cycle(
 | 
				
			||||||
        # are diffed on each draw cycle anyway; so updates to the
 | 
					        # are diffed on each draw cycle anyway; so updates to the
 | 
				
			||||||
        # "curve" length is already automatic.
 | 
					        # "curve" length is already automatic.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # compute the first available graphic's x-units-per-pixel
 | 
					 | 
				
			||||||
        xpx = vlm_chart.view.xs_in_px()
 | 
					 | 
				
			||||||
        # print(r)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # increment the view position by the sample offset.
 | 
					        # increment the view position by the sample offset.
 | 
				
			||||||
        i_step = ohlcv.index
 | 
					        i_step = ohlcv.index
 | 
				
			||||||
        i_diff = i_step - vars['i_last']
 | 
					        i_diff = i_step - vars['i_last']
 | 
				
			||||||
| 
						 | 
					@ -337,41 +397,47 @@ def graphics_update_cycle(
 | 
				
			||||||
        # left under the following conditions:
 | 
					        # left under the following conditions:
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            i_diff > 0  # no new sample step
 | 
					            i_diff > 0  # no new sample step
 | 
				
			||||||
            and xpx < 4  # chart is zoomed out very far
 | 
					            and xpx < update_uppx  # chart is zoomed out very far
 | 
				
			||||||
            and r >= i_step  # the last datum isn't in view
 | 
					            and r >= i_step  # the last datum isn't in view
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
 | 
					            # TODO: we should track and compute whether the last
 | 
				
			||||||
 | 
					            # pixel in a curve should show new data based on uppx
 | 
				
			||||||
 | 
					            # and then iff update curves and shift?
 | 
				
			||||||
            chart.increment_view(steps=i_diff)
 | 
					            chart.increment_view(steps=i_diff)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        if vlm_chart:
 | 
				
			||||||
            vlm_chart
 | 
					            # always update y-label
 | 
				
			||||||
            # if zoomed out alot don't update the last "bar"
 | 
					 | 
				
			||||||
            and xpx < 4
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            vlm_chart.update_curve_from_array('volume', array)
 | 
					 | 
				
			||||||
            ds.vlm_sticky.update_from_data(*array[-1][['index', 'volume']])
 | 
					            ds.vlm_sticky.update_from_data(*array[-1][['index', 'volume']])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                mx_vlm_in_view != vars['last_mx_vlm']
 | 
					                # if zoomed out alot don't update the last "bar"
 | 
				
			||||||
                or mx_vlm_in_view > vars['last_mx_vlm']
 | 
					                (xpx < update_uppx or i_diff > 0)
 | 
				
			||||||
 | 
					                # and r >= i_step
 | 
				
			||||||
            ):
 | 
					            ):
 | 
				
			||||||
                # print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}')
 | 
					                vlm_chart.update_curve_from_array('volume', array)
 | 
				
			||||||
                vlm_chart.view._set_yrange(
 | 
					 | 
				
			||||||
                    yrange=(0, mx_vlm_in_view * 1.375)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                vars['last_mx_vlm'] = mx_vlm_in_view
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for curve_name, flow in vlm_chart._flows.items():
 | 
					                if (
 | 
				
			||||||
                update_fsp_chart(
 | 
					                    mx_vlm_in_view != vars['last_mx_vlm']
 | 
				
			||||||
                    vlm_chart,
 | 
					                    or mx_vlm_in_view > vars['last_mx_vlm']
 | 
				
			||||||
                    flow.shm,
 | 
					                ):
 | 
				
			||||||
                    curve_name,
 | 
					                    # print(f'mx vlm: {last_mx_vlm} -> {mx_vlm_in_view}')
 | 
				
			||||||
                    array_key=curve_name,
 | 
					                    vlm_chart.view._set_yrange(
 | 
				
			||||||
                )
 | 
					                        yrange=(0, mx_vlm_in_view * 1.375)
 | 
				
			||||||
                # is this even doing anything?
 | 
					                    )
 | 
				
			||||||
                flow.plot.vb._set_yrange(
 | 
					                    vars['last_mx_vlm'] = mx_vlm_in_view
 | 
				
			||||||
                    autoscale_linked_plots=False,
 | 
					
 | 
				
			||||||
                    name=curve_name,
 | 
					                for curve_name, flow in vlm_chart._flows.items():
 | 
				
			||||||
                )
 | 
					                    update_fsp_chart(
 | 
				
			||||||
 | 
					                        vlm_chart,
 | 
				
			||||||
 | 
					                        flow.shm,
 | 
				
			||||||
 | 
					                        curve_name,
 | 
				
			||||||
 | 
					                        array_key=curve_name,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    # is this even doing anything?
 | 
				
			||||||
 | 
					                    flow.plot.vb._set_yrange(
 | 
				
			||||||
 | 
					                        autoscale_linked_plots=False,
 | 
				
			||||||
 | 
					                        name=curve_name,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ticks_frame = quote.get('ticks', ())
 | 
					        ticks_frame = quote.get('ticks', ())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -418,8 +484,10 @@ def graphics_update_cycle(
 | 
				
			||||||
        # for typ, tick in reversed(lasts.items()):
 | 
					        # for typ, tick in reversed(lasts.items()):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # update ohlc sampled price bars
 | 
					        # update ohlc sampled price bars
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
        if xpx < 4 or i_diff > 0 :
 | 
					            xpx < update_uppx
 | 
				
			||||||
 | 
					            or i_diff > 0
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
            chart.update_ohlc_from_array(
 | 
					            chart.update_ohlc_from_array(
 | 
				
			||||||
                chart.name,
 | 
					                chart.name,
 | 
				
			||||||
                array,
 | 
					                array,
 | 
				
			||||||
| 
						 | 
					@ -586,8 +654,8 @@ async def display_symbol_data(
 | 
				
			||||||
            f'step:1s '
 | 
					            f'step:1s '
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        linkedsplits = godwidget.linkedsplits
 | 
					        linked = godwidget.linkedsplits
 | 
				
			||||||
        linkedsplits._symbol = symbol
 | 
					        linked._symbol = symbol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # generate order mode side-pane UI
 | 
					        # generate order mode side-pane UI
 | 
				
			||||||
        # A ``FieldsForm`` form to configure order entry
 | 
					        # A ``FieldsForm`` form to configure order entry
 | 
				
			||||||
| 
						 | 
					@ -597,7 +665,7 @@ async def display_symbol_data(
 | 
				
			||||||
        godwidget.pp_pane = pp_pane
 | 
					        godwidget.pp_pane = pp_pane
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # create main OHLC chart
 | 
					        # create main OHLC chart
 | 
				
			||||||
        chart = linkedsplits.plot_ohlc_main(
 | 
					        chart = linked.plot_ohlc_main(
 | 
				
			||||||
            symbol,
 | 
					            symbol,
 | 
				
			||||||
            bars,
 | 
					            bars,
 | 
				
			||||||
            sidepane=pp_pane,
 | 
					            sidepane=pp_pane,
 | 
				
			||||||
| 
						 | 
					@ -627,8 +695,8 @@ async def display_symbol_data(
 | 
				
			||||||
        # NOTE: we must immediately tell Qt to show the OHLC chart
 | 
					        # NOTE: we must immediately tell Qt to show the OHLC chart
 | 
				
			||||||
        # to avoid a race where the subplots get added/shown to
 | 
					        # to avoid a race where the subplots get added/shown to
 | 
				
			||||||
        # the linked set *before* the main price chart!
 | 
					        # the linked set *before* the main price chart!
 | 
				
			||||||
        linkedsplits.show()
 | 
					        linked.show()
 | 
				
			||||||
        linkedsplits.focus()
 | 
					        linked.focus()
 | 
				
			||||||
        await trio.sleep(0)
 | 
					        await trio.sleep(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        vlm_chart: Optional[ChartPlotWidget] = None
 | 
					        vlm_chart: Optional[ChartPlotWidget] = None
 | 
				
			||||||
| 
						 | 
					@ -638,7 +706,7 @@ async def display_symbol_data(
 | 
				
			||||||
            if has_vlm(ohlcv):
 | 
					            if has_vlm(ohlcv):
 | 
				
			||||||
                vlm_chart = await ln.start(
 | 
					                vlm_chart = await ln.start(
 | 
				
			||||||
                    open_vlm_displays,
 | 
					                    open_vlm_displays,
 | 
				
			||||||
                    linkedsplits,
 | 
					                    linked,
 | 
				
			||||||
                    ohlcv,
 | 
					                    ohlcv,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -646,7 +714,7 @@ async def display_symbol_data(
 | 
				
			||||||
            # from an input config.
 | 
					            # from an input config.
 | 
				
			||||||
            ln.start_soon(
 | 
					            ln.start_soon(
 | 
				
			||||||
                start_fsp_displays,
 | 
					                start_fsp_displays,
 | 
				
			||||||
                linkedsplits,
 | 
					                linked,
 | 
				
			||||||
                ohlcv,
 | 
					                ohlcv,
 | 
				
			||||||
                loading_sym_key,
 | 
					                loading_sym_key,
 | 
				
			||||||
                loglevel,
 | 
					                loglevel,
 | 
				
			||||||
| 
						 | 
					@ -655,7 +723,7 @@ async def display_symbol_data(
 | 
				
			||||||
            # start graphics update loop after receiving first live quote
 | 
					            # start graphics update loop after receiving first live quote
 | 
				
			||||||
            ln.start_soon(
 | 
					            ln.start_soon(
 | 
				
			||||||
                graphics_update_loop,
 | 
					                graphics_update_loop,
 | 
				
			||||||
                linkedsplits,
 | 
					                linked,
 | 
				
			||||||
                feed.stream,
 | 
					                feed.stream,
 | 
				
			||||||
                ohlcv,
 | 
					                ohlcv,
 | 
				
			||||||
                wap_in_history,
 | 
					                wap_in_history,
 | 
				
			||||||
| 
						 | 
					@ -674,17 +742,19 @@ async def display_symbol_data(
 | 
				
			||||||
                # let Qt run to render all widgets and make sure the
 | 
					                # let Qt run to render all widgets and make sure the
 | 
				
			||||||
                # sidepanes line up vertically.
 | 
					                # sidepanes line up vertically.
 | 
				
			||||||
                await trio.sleep(0)
 | 
					                await trio.sleep(0)
 | 
				
			||||||
                linkedsplits.resize_sidepanes()
 | 
					                linked.resize_sidepanes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # NOTE: we pop the volume chart from the subplots set so
 | 
					                # NOTE: we pop the volume chart from the subplots set so
 | 
				
			||||||
                # that it isn't double rendered in the display loop
 | 
					                # that it isn't double rendered in the display loop
 | 
				
			||||||
                # above since we do a maxmin calc on the volume data to
 | 
					                # above since we do a maxmin calc on the volume data to
 | 
				
			||||||
                # determine if auto-range adjustements should be made.
 | 
					                # determine if auto-range adjustements should be made.
 | 
				
			||||||
                linkedsplits.subplots.pop('volume', None)
 | 
					                linked.subplots.pop('volume', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # TODO: make this not so shit XD
 | 
					                # TODO: make this not so shit XD
 | 
				
			||||||
                # close group status
 | 
					                # close group status
 | 
				
			||||||
                sbar._status_groups[loading_sym_key][1]()
 | 
					                sbar._status_groups[loading_sym_key][1]()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # let the app run.. bby
 | 
					                # let the app run.. bby
 | 
				
			||||||
 | 
					                chart.default_view()
 | 
				
			||||||
 | 
					                # linked.graphics_cycle()
 | 
				
			||||||
                await trio.sleep_forever()
 | 
					                await trio.sleep_forever()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue