Support (sub)plot names separate from data array keys
							parent
							
								
									4d66c7ad88
								
							
						
					
					
						commit
						a68f4b0593
					
				| 
						 | 
				
			
			@ -299,13 +299,17 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
    def set_split_sizes(
 | 
			
		||||
        self,
 | 
			
		||||
        prop: float = 0.375  # proportion allocated to consumer subcharts
 | 
			
		||||
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        """Set the proportion of space allocated for linked subcharts.
 | 
			
		||||
        """
 | 
			
		||||
        '''Set the proportion of space allocated for linked subcharts.
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
        major = 1 - prop
 | 
			
		||||
        min_h_ind = int((self.height() * prop) / len(self.subplots))
 | 
			
		||||
 | 
			
		||||
        sizes = [int(self.height() * major)]
 | 
			
		||||
        sizes.extend([min_h_ind] * len(self.subplots))
 | 
			
		||||
 | 
			
		||||
        self.splitter.setSizes(sizes)  # , int(self.height()*0.2)
 | 
			
		||||
 | 
			
		||||
    def focus(self) -> None:
 | 
			
		||||
| 
						 | 
				
			
			@ -318,9 +322,12 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
 | 
			
		||||
    def plot_ohlc_main(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        symbol: Symbol,
 | 
			
		||||
        array: np.ndarray,
 | 
			
		||||
 | 
			
		||||
        style: str = 'bar',
 | 
			
		||||
 | 
			
		||||
    ) -> 'ChartPlotWidget':
 | 
			
		||||
        """Start up and show main (price) chart and all linked subcharts.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -352,17 +359,23 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
 | 
			
		||||
    def add_plot(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        name: str,
 | 
			
		||||
        array: np.ndarray,
 | 
			
		||||
        xaxis: DynamicDateAxis = None,
 | 
			
		||||
 | 
			
		||||
        array_key: Optional[str] = None,
 | 
			
		||||
        # xaxis: Optional[DynamicDateAxis] = None,
 | 
			
		||||
        style: str = 'line',
 | 
			
		||||
        _is_main: bool = False,
 | 
			
		||||
 | 
			
		||||
        **cpw_kwargs,
 | 
			
		||||
 | 
			
		||||
    ) -> 'ChartPlotWidget':
 | 
			
		||||
        """Add (sub)plots to chart widget by name.
 | 
			
		||||
        '''Add (sub)plots to chart widget by name.
 | 
			
		||||
 | 
			
		||||
        If ``name`` == ``"main"`` the chart will be the the primary view.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
        if self.chart is None and not _is_main:
 | 
			
		||||
            raise RuntimeError(
 | 
			
		||||
                "A main plot must be created first with `.plot_ohlc_main()`")
 | 
			
		||||
| 
						 | 
				
			
			@ -372,17 +385,25 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
        cv.linkedsplits = self
 | 
			
		||||
 | 
			
		||||
        # use "indicator axis" by default
 | 
			
		||||
        if xaxis is None:
 | 
			
		||||
            xaxis = DynamicDateAxis(
 | 
			
		||||
                orientation='bottom',
 | 
			
		||||
                linkedsplits=self
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # TODO: we gotta possibly assign this back
 | 
			
		||||
        # to the last subplot on removal of some last subplot
 | 
			
		||||
 | 
			
		||||
        xaxis = DynamicDateAxis(
 | 
			
		||||
            orientation='bottom',
 | 
			
		||||
            linkedsplits=self
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if self.xaxis:
 | 
			
		||||
            self.xaxis.hide()
 | 
			
		||||
            self.xaxis = xaxis
 | 
			
		||||
 | 
			
		||||
        cpw = ChartPlotWidget(
 | 
			
		||||
 | 
			
		||||
            # this name will be used to register the primary
 | 
			
		||||
            # graphics curve managed by the subchart
 | 
			
		||||
            name=name,
 | 
			
		||||
            data_key=array_key or name,
 | 
			
		||||
 | 
			
		||||
            array=array,
 | 
			
		||||
            parent=self.splitter,
 | 
			
		||||
| 
						 | 
				
			
			@ -395,7 +416,6 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
            viewBox=cv,
 | 
			
		||||
            **cpw_kwargs,
 | 
			
		||||
        )
 | 
			
		||||
        print(f'xaxis ps: {xaxis.pos()}')
 | 
			
		||||
 | 
			
		||||
        # give viewbox as reference to chart
 | 
			
		||||
        # allowing for kb controls and interactions on **this** widget
 | 
			
		||||
| 
						 | 
				
			
			@ -416,10 +436,10 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
 | 
			
		||||
        # draw curve graphics
 | 
			
		||||
        if style == 'bar':
 | 
			
		||||
            cpw.draw_ohlc(name, array)
 | 
			
		||||
            cpw.draw_ohlc(name, array, array_key=array_key)
 | 
			
		||||
 | 
			
		||||
        elif style == 'line':
 | 
			
		||||
            cpw.draw_curve(name, array)
 | 
			
		||||
            cpw.draw_curve(name, array, array_key=array_key)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError(f"Chart style {style} is currently unsupported")
 | 
			
		||||
| 
						 | 
				
			
			@ -428,11 +448,12 @@ class LinkedSplits(QtWidgets.QWidget):
 | 
			
		|||
            # track by name
 | 
			
		||||
            self.subplots[name] = cpw
 | 
			
		||||
 | 
			
		||||
            # XXX: we need this right?
 | 
			
		||||
            self.splitter.addWidget(cpw)
 | 
			
		||||
 | 
			
		||||
            # scale split regions
 | 
			
		||||
            self.set_split_sizes()
 | 
			
		||||
 | 
			
		||||
            # XXX: we need this right?
 | 
			
		||||
            # self.splitter.addWidget(cpw)
 | 
			
		||||
        else:
 | 
			
		||||
            assert style == 'bar', 'main chart must be OHLC'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -465,9 +486,11 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        # the data view we generate graphics from
 | 
			
		||||
 | 
			
		||||
        # the "data view" we generate graphics from
 | 
			
		||||
        name: str,
 | 
			
		||||
        array: np.ndarray,
 | 
			
		||||
        data_key: str,
 | 
			
		||||
        linkedsplits: LinkedSplits,
 | 
			
		||||
 | 
			
		||||
        view_color: str = 'papas_special',
 | 
			
		||||
| 
						 | 
				
			
			@ -491,6 +514,7 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.data_key = data_key
 | 
			
		||||
        self.linked = linkedsplits
 | 
			
		||||
 | 
			
		||||
        # scene-local placeholder for book graphics
 | 
			
		||||
| 
						 | 
				
			
			@ -618,8 +642,12 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
    def draw_ohlc(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        name: str,
 | 
			
		||||
        data: np.ndarray,
 | 
			
		||||
 | 
			
		||||
        array_key: Optional[str] = None,
 | 
			
		||||
 | 
			
		||||
    ) -> pg.GraphicsObject:
 | 
			
		||||
        """
 | 
			
		||||
        Draw OHLC datums to chart.
 | 
			
		||||
| 
						 | 
				
			
			@ -637,7 +665,8 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
        # draw after to allow self.scene() to work...
 | 
			
		||||
        graphics.draw_from_data(data)
 | 
			
		||||
 | 
			
		||||
        self._graphics[name] = graphics
 | 
			
		||||
        data_key = array_key or name
 | 
			
		||||
        self._graphics[data_key] = graphics
 | 
			
		||||
 | 
			
		||||
        self.linked.cursor.contents_labels.add_label(
 | 
			
		||||
            self,
 | 
			
		||||
| 
						 | 
				
			
			@ -652,12 +681,17 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
    def draw_curve(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        name: str,
 | 
			
		||||
        data: np.ndarray,
 | 
			
		||||
 | 
			
		||||
        array_key: Optional[str] = None,
 | 
			
		||||
        overlay: bool = False,
 | 
			
		||||
        color: str = 'default_light',
 | 
			
		||||
        add_label: bool = True,
 | 
			
		||||
 | 
			
		||||
        **pdi_kwargs,
 | 
			
		||||
 | 
			
		||||
    ) -> pg.PlotDataItem:
 | 
			
		||||
        """Draw a "curve" (line plot graphics) for the provided data in
 | 
			
		||||
        the input array ``data``.
 | 
			
		||||
| 
						 | 
				
			
			@ -668,10 +702,11 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
        }
 | 
			
		||||
        pdi_kwargs.update(_pdi_defaults)
 | 
			
		||||
 | 
			
		||||
        data_key = array_key or name
 | 
			
		||||
        # curve = pg.PlotDataItem(
 | 
			
		||||
        # curve = pg.PlotCurveItem(
 | 
			
		||||
        curve = FastAppendCurve(
 | 
			
		||||
            y=data[name],
 | 
			
		||||
            y=data[data_key],
 | 
			
		||||
            x=data['index'],
 | 
			
		||||
            # antialias=True,
 | 
			
		||||
            name=name,
 | 
			
		||||
| 
						 | 
				
			
			@ -730,8 +765,10 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
    def _add_sticky(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        name: str,
 | 
			
		||||
        bg_color='bracket',
 | 
			
		||||
 | 
			
		||||
    ) -> YAxisLabel:
 | 
			
		||||
 | 
			
		||||
        # if the sticky is for our symbol
 | 
			
		||||
| 
						 | 
				
			
			@ -769,18 +806,23 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
    def update_curve_from_array(
 | 
			
		||||
        self,
 | 
			
		||||
 | 
			
		||||
        name: str,
 | 
			
		||||
        array: np.ndarray,
 | 
			
		||||
        array_key: Optional[str] = None,
 | 
			
		||||
 | 
			
		||||
        **kwargs,
 | 
			
		||||
 | 
			
		||||
    ) -> pg.GraphicsObject:
 | 
			
		||||
        """Update the named internal graphics from ``array``.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        data_key = array_key or name
 | 
			
		||||
        if name not in self._overlays:
 | 
			
		||||
            self._arrays['ohlc'] = array
 | 
			
		||||
        else:
 | 
			
		||||
            self._arrays[name] = array
 | 
			
		||||
            self._arrays[data_key] = array
 | 
			
		||||
 | 
			
		||||
        curve = self._graphics[name]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -790,7 +832,7 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
            # one place to dig around this might be the `QBackingStore`
 | 
			
		||||
            # https://doc.qt.io/qt-5/qbackingstore.html
 | 
			
		||||
            # curve.setData(y=array[name], x=array['index'], **kwargs)
 | 
			
		||||
            curve.update_from_array(x=array['index'], y=array[name], **kwargs)
 | 
			
		||||
            curve.update_from_array(x=array['index'], y=array[data_key], **kwargs)
 | 
			
		||||
 | 
			
		||||
        return curve
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,8 +1103,7 @@ async def chart_from_quotes(
 | 
			
		|||
 | 
			
		||||
                    if wap_in_history:
 | 
			
		||||
                        # update vwap overlay line
 | 
			
		||||
                        chart.update_curve_from_array(
 | 
			
		||||
                            'bar_wap', ohlcv.array)
 | 
			
		||||
                        chart.update_curve_from_array('bar_wap', ohlcv.array)
 | 
			
		||||
 | 
			
		||||
                # l1 book events
 | 
			
		||||
                # throttle the book graphics updates at a lower rate
 | 
			
		||||
| 
						 | 
				
			
			@ -1167,9 +1208,9 @@ async def spawn_fsps(
 | 
			
		|||
            # Currently we spawn an actor per fsp chain but
 | 
			
		||||
            # likely we'll want to pool them eventually to
 | 
			
		||||
            # scale horizonatlly once cores are used up.
 | 
			
		||||
            for fsp_func_name, conf in fsps.items():
 | 
			
		||||
            for display_name, conf in fsps.items():
 | 
			
		||||
 | 
			
		||||
                display_name = f'fsp.{fsp_func_name}'
 | 
			
		||||
                fsp_func_name = conf['fsp_func_name']
 | 
			
		||||
 | 
			
		||||
                # TODO: load function here and introspect
 | 
			
		||||
                # return stream type(s)
 | 
			
		||||
| 
						 | 
				
			
			@ -1177,7 +1218,7 @@ async def spawn_fsps(
 | 
			
		|||
                # TODO: should `index` be a required internal field?
 | 
			
		||||
                fsp_dtype = np.dtype([('index', int), (fsp_func_name, float)])
 | 
			
		||||
 | 
			
		||||
                key = f'{sym}.' + display_name
 | 
			
		||||
                key = f'{sym}.fsp.' + display_name
 | 
			
		||||
 | 
			
		||||
                # this is all sync currently
 | 
			
		||||
                shm, opened = maybe_open_shm_array(
 | 
			
		||||
| 
						 | 
				
			
			@ -1195,7 +1236,7 @@ async def spawn_fsps(
 | 
			
		|||
 | 
			
		||||
                portal = await n.start_actor(
 | 
			
		||||
                    enable_modules=['piker.fsp'],
 | 
			
		||||
                    name=display_name,
 | 
			
		||||
                    name='fsp.' + display_name,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                # init async
 | 
			
		||||
| 
						 | 
				
			
			@ -1234,7 +1275,7 @@ async def run_fsp(
 | 
			
		|||
    config map.
 | 
			
		||||
    """
 | 
			
		||||
    done = linkedsplits.window().status_bar.open_status(
 | 
			
		||||
        f'loading {display_name}..',
 | 
			
		||||
        f'loading fsp, {display_name}..',
 | 
			
		||||
        group_key=group_status_key,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1270,9 +1311,11 @@ async def run_fsp(
 | 
			
		|||
        else:
 | 
			
		||||
 | 
			
		||||
            chart = linkedsplits.add_plot(
 | 
			
		||||
                name=fsp_func_name,
 | 
			
		||||
                name=display_name,
 | 
			
		||||
                array=shm.array,
 | 
			
		||||
 | 
			
		||||
                array_key=conf['fsp_func_name'],
 | 
			
		||||
 | 
			
		||||
                # curve by default
 | 
			
		||||
                ohlc=False,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,14 +1337,19 @@ async def run_fsp(
 | 
			
		|||
 | 
			
		||||
            # read from last calculated value
 | 
			
		||||
            array = shm.array
 | 
			
		||||
 | 
			
		||||
            # XXX: fsp func names are unique meaning we don't have
 | 
			
		||||
            # duplicates of the underlying data even if multiple
 | 
			
		||||
            # sub-charts reference it under different 'named charts'.
 | 
			
		||||
            value = array[fsp_func_name][-1]
 | 
			
		||||
 | 
			
		||||
            last_val_sticky.update_from_data(-1, value)
 | 
			
		||||
 | 
			
		||||
        chart.linked.focus()
 | 
			
		||||
 | 
			
		||||
        # works also for overlays in which case data is looked up from
 | 
			
		||||
        # internal chart array set....
 | 
			
		||||
        chart.update_curve_from_array(fsp_func_name, shm.array)
 | 
			
		||||
        chart.update_curve_from_array(display_name, shm.array, array_key=fsp_func_name)
 | 
			
		||||
 | 
			
		||||
        # TODO: figure out if we can roll our own `FillToThreshold` to
 | 
			
		||||
        # get brush filled polygons for OS/OB conditions.
 | 
			
		||||
| 
						 | 
				
			
			@ -1365,7 +1413,11 @@ async def run_fsp(
 | 
			
		|||
                last_val_sticky.update_from_data(-1, value)
 | 
			
		||||
 | 
			
		||||
            # update graphics
 | 
			
		||||
            chart.update_curve_from_array(fsp_func_name, array)
 | 
			
		||||
            chart.update_curve_from_array(
 | 
			
		||||
                display_name,
 | 
			
		||||
                array,
 | 
			
		||||
                array_key=fsp_func_name,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # set time of last graphics update
 | 
			
		||||
            last = now
 | 
			
		||||
| 
						 | 
				
			
			@ -1420,7 +1472,11 @@ async def check_for_new_bars(feed, ohlcv, linkedsplits):
 | 
			
		|||
                )
 | 
			
		||||
 | 
			
		||||
            for name, chart in linkedsplits.subplots.items():
 | 
			
		||||
                chart.update_curve_from_array(chart.name, chart._shm.array)
 | 
			
		||||
                chart.update_curve_from_array(
 | 
			
		||||
                    chart.name,
 | 
			
		||||
                    chart._shm.array,
 | 
			
		||||
                    array_key=chart.data_key
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            # shift the view if in follow mode
 | 
			
		||||
            price_chart.increment_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -1510,6 +1566,14 @@ async def display_symbol_data(
 | 
			
		|||
        # TODO: eventually we'll support some kind of n-compose syntax
 | 
			
		||||
        fsp_conf = {
 | 
			
		||||
            'rsi': {
 | 
			
		||||
                'fsp_func_name': 'rsi',
 | 
			
		||||
                'period': 14,
 | 
			
		||||
                'chart_kwargs': {
 | 
			
		||||
                    'static_yrange': (0, 100),
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            'rsi2': {
 | 
			
		||||
                'fsp_func_name': 'rsi',
 | 
			
		||||
                'period': 14,
 | 
			
		||||
                'chart_kwargs': {
 | 
			
		||||
                    'static_yrange': (0, 100),
 | 
			
		||||
| 
						 | 
				
			
			@ -1532,6 +1596,7 @@ async def display_symbol_data(
 | 
			
		|||
        else:
 | 
			
		||||
            fsp_conf.update({
 | 
			
		||||
                'vwap': {
 | 
			
		||||
                    'fsp_func_name': 'vwap',
 | 
			
		||||
                    'overlay': True,
 | 
			
		||||
                    'anchor': 'session',
 | 
			
		||||
                },
 | 
			
		||||
| 
						 | 
				
			
			@ -1723,7 +1788,7 @@ async def _async_main(
 | 
			
		|||
                        'allocator': {
 | 
			
		||||
                            'key': '**allocator**:',
 | 
			
		||||
                            'type': 'select',
 | 
			
		||||
                            'default_value': ['$ size', '% of port',],
 | 
			
		||||
                            'default_value': ['$ size', '% of port', '# shares'],
 | 
			
		||||
                        },
 | 
			
		||||
                        'dollar_size': {
 | 
			
		||||
                            'key': '**$size**:',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue