Move L1 labels into lone module
							parent
							
								
									8997b6029b
								
							
						
					
					
						commit
						a1a1dec885
					
				| 
						 | 
					@ -41,8 +41,8 @@ from ._graphics._cursor import (
 | 
				
			||||||
from ._graphics._lines import (
 | 
					from ._graphics._lines import (
 | 
				
			||||||
    level_line,
 | 
					    level_line,
 | 
				
			||||||
    order_line,
 | 
					    order_line,
 | 
				
			||||||
    L1Labels,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from ._l1 import L1Labels
 | 
				
			||||||
from ._graphics._ohlc import BarItems
 | 
					from ._graphics._ohlc import BarItems
 | 
				
			||||||
from ._graphics._curve import FastAppendCurve
 | 
					from ._graphics._curve import FastAppendCurve
 | 
				
			||||||
from ._style import (
 | 
					from ._style import (
 | 
				
			||||||
| 
						 | 
					@ -1105,7 +1105,7 @@ async def _async_main(
 | 
				
			||||||
                        ):
 | 
					                        ):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # show line label once order is live
 | 
					                            # show line label once order is live
 | 
				
			||||||
                            line = order_mode.on_submit(oid)
 | 
					                            order_mode.on_submit(oid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        # resp to 'cancel' request or error condition
 | 
					                        # resp to 'cancel' request or error condition
 | 
				
			||||||
                        # for action request
 | 
					                        # for action request
 | 
				
			||||||
| 
						 | 
					@ -1132,7 +1132,7 @@ async def _async_main(
 | 
				
			||||||
                                    price=msg['trigger_price'],
 | 
					                                    price=msg['trigger_price'],
 | 
				
			||||||
                                    arrow_index=get_index(time.time())
 | 
					                                    arrow_index=get_index(time.time())
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
                                line = await order_mode.on_exec(oid, msg)
 | 
					                                await order_mode.on_exec(oid, msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        # response to completed 'action' request for buy/sell
 | 
					                        # response to completed 'action' request for buy/sell
 | 
				
			||||||
                        elif resp in (
 | 
					                        elif resp in (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,274 +22,12 @@ from typing import Tuple, Optional, List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pyqtgraph as pg
 | 
					import pyqtgraph as pg
 | 
				
			||||||
from PyQt5 import QtCore, QtGui
 | 
					from PyQt5 import QtCore, QtGui
 | 
				
			||||||
from PyQt5.QtCore import QPointF
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .._label import Label, vbr_left, right_axis
 | 
					from .._label import Label, vbr_left, right_axis
 | 
				
			||||||
from .._style import (
 | 
					from .._style import (
 | 
				
			||||||
    hcolor,
 | 
					    hcolor,
 | 
				
			||||||
    _down_2_font_inches_we_like,
 | 
					    _down_2_font_inches_we_like,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .._axes import YAxisLabel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LevelLabel(YAxisLabel):
 | 
					 | 
				
			||||||
    """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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # adjustment "further away from" anchor point
 | 
					 | 
				
			||||||
    _x_offset = 9
 | 
					 | 
				
			||||||
    _y_offset = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # fields to be displayed in the label string
 | 
					 | 
				
			||||||
    _fields = {
 | 
					 | 
				
			||||||
        'level': 0,
 | 
					 | 
				
			||||||
        'level_digits': 2,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    # default label template is just a y-level with so much precision
 | 
					 | 
				
			||||||
    _fmt_str = '{level:,.{level_digits}f}'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        chart,
 | 
					 | 
				
			||||||
        parent,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        color: str = 'bracket',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        orient_v: str = 'bottom',
 | 
					 | 
				
			||||||
        orient_h: str = 'left',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        opacity: float = 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # makes order line labels offset from their parent axis
 | 
					 | 
				
			||||||
        # such that they don't collide with the L1/L2 lines/prices
 | 
					 | 
				
			||||||
        # that are displayed on the axis
 | 
					 | 
				
			||||||
        adjust_to_l1: bool = False,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        **axis_label_kwargs,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super().__init__(
 | 
					 | 
				
			||||||
            chart,
 | 
					 | 
				
			||||||
            parent=parent,
 | 
					 | 
				
			||||||
            use_arrow=False,
 | 
					 | 
				
			||||||
            opacity=opacity,
 | 
					 | 
				
			||||||
            **axis_label_kwargs
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # TODO: this is kinda cludgy
 | 
					 | 
				
			||||||
        self._hcolor: pg.Pen = None
 | 
					 | 
				
			||||||
        self.color: str = color
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # orientation around axis options
 | 
					 | 
				
			||||||
        self._orient_v = orient_v
 | 
					 | 
				
			||||||
        self._orient_h = orient_h
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._adjust_to_l1 = adjust_to_l1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._v_shift = {
 | 
					 | 
				
			||||||
            'top': -1.,
 | 
					 | 
				
			||||||
            'bottom': 0.,
 | 
					 | 
				
			||||||
            'middle': 1 / 2.
 | 
					 | 
				
			||||||
        }[orient_v]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._h_shift = {
 | 
					 | 
				
			||||||
            'left': -1.,
 | 
					 | 
				
			||||||
            '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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def color(self):
 | 
					 | 
				
			||||||
        return self._hcolor
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @color.setter
 | 
					 | 
				
			||||||
    def color(self, color: str) -> None:
 | 
					 | 
				
			||||||
        self._hcolor = color
 | 
					 | 
				
			||||||
        self._pen = self.pen = pg.mkPen(hcolor(color))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_on_resize(self, vr, r):
 | 
					 | 
				
			||||||
        """Tiis is a ``.sigRangeChanged()`` handler.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.update_fields(self.fields)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_fields(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        fields: dict = None,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        """Update the label's text contents **and** position from
 | 
					 | 
				
			||||||
        a view box coordinate datum.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.fields.update(fields)
 | 
					 | 
				
			||||||
        level = self.fields['level']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # map "level" to local coords
 | 
					 | 
				
			||||||
        abs_xy = self._chart.mapFromView(QPointF(0, level))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.update_label(
 | 
					 | 
				
			||||||
            abs_xy,
 | 
					 | 
				
			||||||
            self.fields,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_label(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        abs_pos: QPointF,  # scene coords
 | 
					 | 
				
			||||||
        fields: dict,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # write contents, type specific
 | 
					 | 
				
			||||||
        h, w = self.set_label_str(fields)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._adjust_to_l1:
 | 
					 | 
				
			||||||
            self._x_offset = _max_l1_line_len
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.setPos(QPointF(
 | 
					 | 
				
			||||||
            self._h_shift * (w + self._x_offset),
 | 
					 | 
				
			||||||
            abs_pos.y() + self._v_shift * h
 | 
					 | 
				
			||||||
        ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def set_fmt_str(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        fmt_str: str,
 | 
					 | 
				
			||||||
        fields: dict,
 | 
					 | 
				
			||||||
    ) -> (str, str):
 | 
					 | 
				
			||||||
        # test that new fmt str can be rendered
 | 
					 | 
				
			||||||
        self._fmt_str = fmt_str
 | 
					 | 
				
			||||||
        self.set_label_str(fields)
 | 
					 | 
				
			||||||
        self.fields.update(fields)
 | 
					 | 
				
			||||||
        return fmt_str, self.label_str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def set_label_str(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        fields: dict,
 | 
					 | 
				
			||||||
    ):
 | 
					 | 
				
			||||||
        # use space as e3 delim
 | 
					 | 
				
			||||||
        self.label_str = self._fmt_str.format(**fields).replace(',', ' ')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        br = self.boundingRect()
 | 
					 | 
				
			||||||
        h, w = br.height(), br.width()
 | 
					 | 
				
			||||||
        return h, w
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def size_hint(self) -> Tuple[None, None]:
 | 
					 | 
				
			||||||
        return None, None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def draw(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        p: QtGui.QPainter,
 | 
					 | 
				
			||||||
        rect: QtCore.QRectF
 | 
					 | 
				
			||||||
    ) -> 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()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        p.drawLine(lp.x(), lp.y(), rp.x(), rp.y())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def highlight(self, pen) -> None:
 | 
					 | 
				
			||||||
        self._pen = pen
 | 
					 | 
				
			||||||
        self.update()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def unhighlight(self):
 | 
					 | 
				
			||||||
        self._pen = self.pen
 | 
					 | 
				
			||||||
        self.update()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# global for now but probably should be
 | 
					 | 
				
			||||||
# attached to chart instance?
 | 
					 | 
				
			||||||
_max_l1_line_len: float = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class L1Label(LevelLabel):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text_flags = (
 | 
					 | 
				
			||||||
        QtCore.Qt.TextDontClip
 | 
					 | 
				
			||||||
        | QtCore.Qt.AlignLeft
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def set_label_str(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        fields: dict,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
        """Make sure the max L1 line module var is kept up to date.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        h, w = super().set_label_str(fields)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Set a global "max L1 label length" so we can look it up
 | 
					 | 
				
			||||||
        # on order lines and adjust their labels not to overlap with it.
 | 
					 | 
				
			||||||
        global _max_l1_line_len
 | 
					 | 
				
			||||||
        _max_l1_line_len = max(_max_l1_line_len, w)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return h, w
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class L1Labels:
 | 
					 | 
				
			||||||
    """Level 1 bid ask labels for dynamic update on price-axis.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    def __init__(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        chart: 'ChartPlotWidget',  # noqa
 | 
					 | 
				
			||||||
        digits: int = 2,
 | 
					 | 
				
			||||||
        size_digits: int = 3,
 | 
					 | 
				
			||||||
        font_size_inches: float = _down_2_font_inches_we_like,
 | 
					 | 
				
			||||||
    ) -> None:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.chart = chart
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        raxis = chart.getAxis('right')
 | 
					 | 
				
			||||||
        kwargs = {
 | 
					 | 
				
			||||||
            'chart': chart,
 | 
					 | 
				
			||||||
            'parent': raxis,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            'opacity': 1,
 | 
					 | 
				
			||||||
            'font_size_inches': font_size_inches,
 | 
					 | 
				
			||||||
            'fg_color': chart.pen_color,
 | 
					 | 
				
			||||||
            'bg_color': chart.view_color,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fmt_str = (
 | 
					 | 
				
			||||||
            '{size:.{size_digits}f} x '
 | 
					 | 
				
			||||||
            '{level:,.{level_digits}f}'
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        fields = {
 | 
					 | 
				
			||||||
            'level': 0,
 | 
					 | 
				
			||||||
            'level_digits': digits,
 | 
					 | 
				
			||||||
            'size': 0,
 | 
					 | 
				
			||||||
            'size_digits': size_digits,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        bid = self.bid_label = L1Label(
 | 
					 | 
				
			||||||
            orient_v='bottom',
 | 
					 | 
				
			||||||
            **kwargs,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        bid.set_fmt_str(fmt_str=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.show()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: probably worth investigating if we can
 | 
					# TODO: probably worth investigating if we can
 | 
				
			||||||
| 
						 | 
					@ -391,7 +129,7 @@ class LevelLine(pg.InfiniteLine):
 | 
				
			||||||
        bg_color: str = None,
 | 
					        bg_color: str = None,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        **label_kwargs,
 | 
					        **label_kwargs,
 | 
				
			||||||
    ) -> LevelLabel:
 | 
					    ) -> Label:
 | 
				
			||||||
        """Add a ``LevelLabel`` anchored at one of the line endpoints in view.
 | 
					        """Add a ``LevelLabel`` anchored at one of the line endpoints in view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,291 @@
 | 
				
			||||||
 | 
					# piker: trading gear for hackers
 | 
				
			||||||
 | 
					# Copyright (C) Tyler Goodlet (in stewardship for piker0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					# it under the terms of the GNU Affero General Public License as published by
 | 
				
			||||||
 | 
					# the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					# (at your option) any later version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					# GNU Affero General Public License for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# You should have received a copy of the GNU Affero General Public License
 | 
				
			||||||
 | 
					# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Double auction top-of-book (L1) graphics.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from typing import Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pyqtgraph as pg
 | 
				
			||||||
 | 
					from PyQt5 import QtCore, QtGui
 | 
				
			||||||
 | 
					from PyQt5.QtCore import QPointF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ._axes import YAxisLabel
 | 
				
			||||||
 | 
					from ._style import (
 | 
				
			||||||
 | 
					    hcolor,
 | 
				
			||||||
 | 
					    _down_2_font_inches_we_like,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LevelLabel(YAxisLabel):
 | 
				
			||||||
 | 
					    """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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # adjustment "further away from" anchor point
 | 
				
			||||||
 | 
					    _x_offset = 9
 | 
				
			||||||
 | 
					    _y_offset = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # fields to be displayed in the label string
 | 
				
			||||||
 | 
					    _fields = {
 | 
				
			||||||
 | 
					        'level': 0,
 | 
				
			||||||
 | 
					        'level_digits': 2,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    # default label template is just a y-level with so much precision
 | 
				
			||||||
 | 
					    _fmt_str = '{level:,.{level_digits}f}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        chart,
 | 
				
			||||||
 | 
					        parent,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        color: str = 'bracket',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        orient_v: str = 'bottom',
 | 
				
			||||||
 | 
					        orient_h: str = 'left',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        opacity: float = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # makes order line labels offset from their parent axis
 | 
				
			||||||
 | 
					        # such that they don't collide with the L1/L2 lines/prices
 | 
				
			||||||
 | 
					        # that are displayed on the axis
 | 
				
			||||||
 | 
					        adjust_to_l1: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        **axis_label_kwargs,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super().__init__(
 | 
				
			||||||
 | 
					            chart,
 | 
				
			||||||
 | 
					            parent=parent,
 | 
				
			||||||
 | 
					            use_arrow=False,
 | 
				
			||||||
 | 
					            opacity=opacity,
 | 
				
			||||||
 | 
					            **axis_label_kwargs
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: this is kinda cludgy
 | 
				
			||||||
 | 
					        self._hcolor: pg.Pen = None
 | 
				
			||||||
 | 
					        self.color: str = color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # orientation around axis options
 | 
				
			||||||
 | 
					        self._orient_v = orient_v
 | 
				
			||||||
 | 
					        self._orient_h = orient_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._adjust_to_l1 = adjust_to_l1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._v_shift = {
 | 
				
			||||||
 | 
					            'top': -1.,
 | 
				
			||||||
 | 
					            'bottom': 0.,
 | 
				
			||||||
 | 
					            'middle': 1 / 2.
 | 
				
			||||||
 | 
					        }[orient_v]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._h_shift = {
 | 
				
			||||||
 | 
					            'left': -1.,
 | 
				
			||||||
 | 
					            '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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def color(self):
 | 
				
			||||||
 | 
					        return self._hcolor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @color.setter
 | 
				
			||||||
 | 
					    def color(self, color: str) -> None:
 | 
				
			||||||
 | 
					        self._hcolor = color
 | 
				
			||||||
 | 
					        self._pen = self.pen = pg.mkPen(hcolor(color))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_on_resize(self, vr, r):
 | 
				
			||||||
 | 
					        """Tiis is a ``.sigRangeChanged()`` handler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.update_fields(self.fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_fields(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        fields: dict = None,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        """Update the label's text contents **and** position from
 | 
				
			||||||
 | 
					        a view box coordinate datum.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.fields.update(fields)
 | 
				
			||||||
 | 
					        level = self.fields['level']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # map "level" to local coords
 | 
				
			||||||
 | 
					        abs_xy = self._chart.mapFromView(QPointF(0, level))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.update_label(
 | 
				
			||||||
 | 
					            abs_xy,
 | 
				
			||||||
 | 
					            self.fields,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_label(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        abs_pos: QPointF,  # scene coords
 | 
				
			||||||
 | 
					        fields: dict,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # write contents, type specific
 | 
				
			||||||
 | 
					        h, w = self.set_label_str(fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self._adjust_to_l1:
 | 
				
			||||||
 | 
					            self._x_offset = _max_l1_line_len
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.setPos(QPointF(
 | 
				
			||||||
 | 
					            self._h_shift * (w + self._x_offset),
 | 
				
			||||||
 | 
					            abs_pos.y() + self._v_shift * h
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_fmt_str(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        fmt_str: str,
 | 
				
			||||||
 | 
					        fields: dict,
 | 
				
			||||||
 | 
					    ) -> (str, str):
 | 
				
			||||||
 | 
					        # test that new fmt str can be rendered
 | 
				
			||||||
 | 
					        self._fmt_str = fmt_str
 | 
				
			||||||
 | 
					        self.set_label_str(fields)
 | 
				
			||||||
 | 
					        self.fields.update(fields)
 | 
				
			||||||
 | 
					        return fmt_str, self.label_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_label_str(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        fields: dict,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        # use space as e3 delim
 | 
				
			||||||
 | 
					        self.label_str = self._fmt_str.format(**fields).replace(',', ' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        br = self.boundingRect()
 | 
				
			||||||
 | 
					        h, w = br.height(), br.width()
 | 
				
			||||||
 | 
					        return h, w
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def size_hint(self) -> Tuple[None, None]:
 | 
				
			||||||
 | 
					        return None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        p: QtGui.QPainter,
 | 
				
			||||||
 | 
					        rect: QtCore.QRectF
 | 
				
			||||||
 | 
					    ) -> 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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p.drawLine(lp.x(), lp.y(), rp.x(), rp.y())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def highlight(self, pen) -> None:
 | 
				
			||||||
 | 
					        self._pen = pen
 | 
				
			||||||
 | 
					        self.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unhighlight(self):
 | 
				
			||||||
 | 
					        self._pen = self.pen
 | 
				
			||||||
 | 
					        self.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# global for now but probably should be
 | 
				
			||||||
 | 
					# attached to chart instance?
 | 
				
			||||||
 | 
					_max_l1_line_len: float = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class L1Label(LevelLabel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_flags = (
 | 
				
			||||||
 | 
					        QtCore.Qt.TextDontClip
 | 
				
			||||||
 | 
					        | QtCore.Qt.AlignLeft
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_label_str(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        fields: dict,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        """Make sure the max L1 line module var is kept up to date.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        h, w = super().set_label_str(fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set a global "max L1 label length" so we can look it up
 | 
				
			||||||
 | 
					        # on order lines and adjust their labels not to overlap with it.
 | 
				
			||||||
 | 
					        global _max_l1_line_len
 | 
				
			||||||
 | 
					        _max_l1_line_len = max(_max_l1_line_len, w)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return h, w
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class L1Labels:
 | 
				
			||||||
 | 
					    """Level 1 bid ask labels for dynamic update on price-axis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        chart: 'ChartPlotWidget',  # noqa
 | 
				
			||||||
 | 
					        digits: int = 2,
 | 
				
			||||||
 | 
					        size_digits: int = 3,
 | 
				
			||||||
 | 
					        font_size_inches: float = _down_2_font_inches_we_like,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.chart = chart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raxis = chart.getAxis('right')
 | 
				
			||||||
 | 
					        kwargs = {
 | 
				
			||||||
 | 
					            'chart': chart,
 | 
				
			||||||
 | 
					            'parent': raxis,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            'opacity': 1,
 | 
				
			||||||
 | 
					            'font_size_inches': font_size_inches,
 | 
				
			||||||
 | 
					            'fg_color': chart.pen_color,
 | 
				
			||||||
 | 
					            'bg_color': chart.view_color,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fmt_str = (
 | 
				
			||||||
 | 
					            '{size:.{size_digits}f} x '
 | 
				
			||||||
 | 
					            '{level:,.{level_digits}f}'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        fields = {
 | 
				
			||||||
 | 
					            'level': 0,
 | 
				
			||||||
 | 
					            'level_digits': digits,
 | 
				
			||||||
 | 
					            'size': 0,
 | 
				
			||||||
 | 
					            'size_digits': size_digits,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bid = self.bid_label = L1Label(
 | 
				
			||||||
 | 
					            orient_v='bottom',
 | 
				
			||||||
 | 
					            **kwargs,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        bid.set_fmt_str(fmt_str=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.show()
 | 
				
			||||||
		Loading…
	
		Reference in New Issue