Fix oustanding label bugs, make `.update()` accept a position msg
parent
826c4408ea
commit
1abbd095ec
|
@ -18,11 +18,11 @@
|
|||
Position info and display
|
||||
|
||||
"""
|
||||
from typing import Optional, Dict, Any, Callable
|
||||
from typing import Optional, Callable
|
||||
from functools import partial
|
||||
from math import floor
|
||||
|
||||
from pyqtgraph import Point, functions as fn
|
||||
from pyqtgraph import functions as fn
|
||||
from pydantic import BaseModel
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5.QtCore import QPointF
|
||||
|
@ -34,20 +34,28 @@ from ._anchors import (
|
|||
gpath_pin,
|
||||
# keep_marker_in_view,
|
||||
)
|
||||
from ..clearing._messages import BrokerdPosition, Status
|
||||
from ..data._source import Symbol
|
||||
from ._label import Label
|
||||
from ._lines import LevelLine, level_line
|
||||
from ._style import _font
|
||||
from ..data._source import Symbol
|
||||
|
||||
|
||||
class Position(BaseModel):
|
||||
'''Basic pp representation with attached fills history.
|
||||
'''Basic pp (personal position) data representation with attached
|
||||
fills history.
|
||||
|
||||
This type should be IPC wire ready?
|
||||
|
||||
'''
|
||||
symbol: Symbol
|
||||
|
||||
# last size and avg entry price
|
||||
size: float
|
||||
avg_price: float # TODO: contextual pricing
|
||||
fills: Dict[str, Any] = {}
|
||||
|
||||
# ordered record of known constituent trade messages
|
||||
fills: list[Status] = []
|
||||
|
||||
|
||||
class LevelMarker(QGraphicsPathItem):
|
||||
|
@ -65,32 +73,22 @@ class LevelMarker(QGraphicsPathItem):
|
|||
|
||||
) -> None:
|
||||
|
||||
self._style = None
|
||||
self.size = size
|
||||
|
||||
# get polygon and scale
|
||||
super().__init__()
|
||||
self.scale(size, size)
|
||||
|
||||
# interally generates and scales path
|
||||
# interally generates path
|
||||
self._style = None
|
||||
self.style = style
|
||||
# path = mk_marker_path(style)
|
||||
|
||||
# self.scale(size, size)
|
||||
|
||||
self.chart = chart
|
||||
# chart.getViewBox().scene().addItem(self)
|
||||
|
||||
self.get_level = get_level
|
||||
self.scene_x = lambda: marker_right_points(chart)[1]
|
||||
self.level: float = 0
|
||||
self.keep_in_view = keep_in_view
|
||||
|
||||
# get the path for the opaque path **without** weird
|
||||
# surrounding margin
|
||||
self.path_br = self.mapToScene(
|
||||
self.path()
|
||||
).boundingRect()
|
||||
|
||||
assert self.path_br
|
||||
|
||||
@property
|
||||
def style(self) -> str:
|
||||
|
@ -102,7 +100,13 @@ class LevelMarker(QGraphicsPathItem):
|
|||
polygon = mk_marker_path(value)
|
||||
self.setPath(polygon)
|
||||
self._style = value
|
||||
self.scale(self.size, self.size)
|
||||
|
||||
# get the path for the opaque path **without** weird
|
||||
# surrounding margin
|
||||
self.path_br = self.mapToScene(
|
||||
self.path()
|
||||
).boundingRect()
|
||||
|
||||
|
||||
def delete(self) -> None:
|
||||
self.scene().removeItem(self)
|
||||
|
@ -154,8 +158,7 @@ class LevelMarker(QGraphicsPathItem):
|
|||
)
|
||||
|
||||
else:
|
||||
# # pp line is viewable so show marker normally
|
||||
# self.update()
|
||||
# pp line is viewable so show marker normally
|
||||
self.setPos(
|
||||
x,
|
||||
self.chart.view.mapFromView(
|
||||
|
@ -186,9 +189,7 @@ class LevelMarker(QGraphicsPathItem):
|
|||
if self.keep_in_view:
|
||||
self.position_in_view()
|
||||
|
||||
else:
|
||||
|
||||
# just place at desired level even if not in view
|
||||
else: # just place at desired level even if not in view
|
||||
self.setPos(
|
||||
self.scene_x(),
|
||||
self.mapToScene(QPointF(0, self.get_level())).y()
|
||||
|
@ -197,16 +198,18 @@ class LevelMarker(QGraphicsPathItem):
|
|||
return super().paint(p, opt, w)
|
||||
|
||||
|
||||
class PositionInfo:
|
||||
class PositionTracker:
|
||||
'''Track and display a real-time position for a single symbol
|
||||
on a chart.
|
||||
|
||||
'''
|
||||
# inputs
|
||||
chart: 'ChartPlotWidget' # noqa
|
||||
info: dict
|
||||
|
||||
# allocated
|
||||
info: Position
|
||||
pp_label: Label
|
||||
size_label: Label
|
||||
info_label: Label
|
||||
line: Optional[LevelLine] = None
|
||||
|
||||
_color: str = 'default_light'
|
||||
|
@ -217,10 +220,13 @@ class PositionInfo:
|
|||
|
||||
) -> None:
|
||||
|
||||
# from . import _lines
|
||||
|
||||
self.chart = chart
|
||||
self.info = {}
|
||||
self.info = Position(
|
||||
symbol=chart.linked.symbol,
|
||||
size=0,
|
||||
avg_price=0,
|
||||
)
|
||||
|
||||
self.pp_label = None
|
||||
|
||||
view = chart.getViewBox()
|
||||
|
@ -248,14 +254,13 @@ class PositionInfo:
|
|||
pp_label.show()
|
||||
|
||||
self.size_label = size_label = Label(
|
||||
|
||||
view=view,
|
||||
color=self._color,
|
||||
|
||||
# this is "static" label
|
||||
# update_on_range_change=False,
|
||||
fmt_str='\n'.join((
|
||||
'{entry_size} x',
|
||||
'x{entry_size}',
|
||||
)),
|
||||
|
||||
fields={
|
||||
|
@ -264,22 +269,13 @@ class PositionInfo:
|
|||
)
|
||||
size_label.render()
|
||||
# size_label.scene_anchor = self.align_to_marker
|
||||
size_label.scene_anchor = partial(
|
||||
gpath_pin,
|
||||
location_description='left-of-path-centered',
|
||||
gpath=self._level_marker,
|
||||
label=size_label,
|
||||
|
||||
size_label.scene_anchor = lambda: (
|
||||
self.pp_label.txt.pos() + QPointF(self.pp_label.w, 0)
|
||||
)
|
||||
size_label.hide()
|
||||
|
||||
# self.info_label = info_label = Label(
|
||||
|
||||
# view=view,
|
||||
# color=self._color,
|
||||
|
||||
# # this is "static" label
|
||||
# # update_on_range_change=False,
|
||||
|
||||
# TODO: if we want to show more position-y info?
|
||||
# fmt_str='\n'.join((
|
||||
# # '{entry_size}x ',
|
||||
# '{percent_pnl} % PnL',
|
||||
|
@ -294,10 +290,36 @@ class PositionInfo:
|
|||
# 'base_unit_value': '1k',
|
||||
# },
|
||||
# )
|
||||
# info_label.scene_anchor = lambda: self.size_label.txt.pos()
|
||||
# + QPointF(0, self.size_label.h)
|
||||
# info_label.render()
|
||||
# info_label.hide()
|
||||
|
||||
def update(
|
||||
self,
|
||||
msg: BrokerdPosition,
|
||||
|
||||
) -> None:
|
||||
'''Update graphics and data from average price and size.
|
||||
|
||||
'''
|
||||
avg_price, size = msg['avg_price'], msg['size']
|
||||
# info updates
|
||||
self.info.avg_price = avg_price
|
||||
self.info.size = size
|
||||
|
||||
self.update_line(avg_price, size)
|
||||
|
||||
# label updates
|
||||
self.size_label.fields['entry_size'] = size
|
||||
self.size_label.render()
|
||||
|
||||
if size == 0:
|
||||
self.hide()
|
||||
|
||||
else:
|
||||
self._level_marker.level = avg_price
|
||||
self._level_marker.update() # trigger paint
|
||||
self.show()
|
||||
|
||||
# self.pp_label.show()
|
||||
# self._level_marker.show()
|
||||
|
||||
def level(self) -> float:
|
||||
if self.line:
|
||||
|
@ -306,19 +328,25 @@ class PositionInfo:
|
|||
return 0
|
||||
|
||||
def show(self) -> None:
|
||||
self.pp_label.show()
|
||||
self.size_label.show()
|
||||
# self.info_label.show()
|
||||
if self.line:
|
||||
if self.info.size:
|
||||
self.line.show()
|
||||
self._level_marker.show()
|
||||
self.pp_label.show()
|
||||
self.size_label.show()
|
||||
|
||||
def hide(self) -> None:
|
||||
# self.pp_label.hide()
|
||||
self.pp_label.hide()
|
||||
self._level_marker.hide()
|
||||
self.size_label.hide()
|
||||
# self.info_label.hide()
|
||||
if self.line:
|
||||
self.line.hide()
|
||||
|
||||
# if self.line:
|
||||
# self.line.hide()
|
||||
def hide_info(self) -> None:
|
||||
'''Hide details of position.
|
||||
|
||||
'''
|
||||
# TODO: add remove status bar widgets here
|
||||
self.size_label.hide()
|
||||
|
||||
def level_marker(
|
||||
self,
|
||||
|
@ -402,17 +430,8 @@ class PositionInfo:
|
|||
elif size < 0:
|
||||
style = '>|'
|
||||
|
||||
self._level_marker.style = style
|
||||
|
||||
# last_direction = self._level_marker._direction
|
||||
# if (
|
||||
# size < 0 and last_direction == 'up'
|
||||
# ):
|
||||
# self._level_marker = self.level_marker(size)
|
||||
marker = self._level_marker
|
||||
|
||||
# add path to scene
|
||||
# line.getViewBox().scene().addItem(marker)
|
||||
marker.style = style
|
||||
|
||||
# set marker color to same as line
|
||||
marker.setPen(line.currentPen)
|
||||
|
@ -421,23 +440,12 @@ class PositionInfo:
|
|||
marker.update()
|
||||
marker.show()
|
||||
|
||||
# hide position marker when out of view (for now)
|
||||
# show position marker on view "edge" when out of view
|
||||
vb = line.getViewBox()
|
||||
vb.sigRangeChanged.connect(marker.position_in_view)
|
||||
|
||||
line._labels.append(self.pp_label)
|
||||
|
||||
# XXX: uses new marker drawing approach
|
||||
# line.add_marker(self._level_marker)
|
||||
line.set_level(level)
|
||||
|
||||
# sanity check
|
||||
line.update_labels({'level': level})
|
||||
|
||||
# vb.sigRangeChanged.connect(
|
||||
# partial(keep_marker_in_view, chartview=vb, line=line)
|
||||
# )
|
||||
|
||||
return line
|
||||
|
||||
# order line endpoint anchor
|
||||
|
@ -446,10 +454,10 @@ class PositionInfo:
|
|||
pp_line = self.line
|
||||
if pp_line:
|
||||
|
||||
line_ep = pp_line.scene_endpoint()
|
||||
# line_ep = pp_line.scene_endpoint()
|
||||
# print(line_ep)
|
||||
|
||||
y_level_scene = line_ep.y()
|
||||
# y_level_scene = line_ep.y()
|
||||
# pp_y = pp_label.txt.pos().y()
|
||||
|
||||
# if y_level_scene > pp_y:
|
||||
|
@ -494,7 +502,6 @@ class PositionInfo:
|
|||
) -> None:
|
||||
'''Update personal position level line.
|
||||
|
||||
|
||||
'''
|
||||
# do line update
|
||||
line = self.line
|
||||
|
@ -512,38 +519,12 @@ class PositionInfo:
|
|||
|
||||
if size != 0.0:
|
||||
line.set_level(price)
|
||||
self._level_marker.lelvel = price
|
||||
self._level_marker.level = price
|
||||
self._level_marker.update()
|
||||
line.update_labels({'size': size})
|
||||
# line.update_labels({'size': size})
|
||||
line.show()
|
||||
|
||||
else:
|
||||
# remove pp line from view
|
||||
line.delete()
|
||||
self.line = None
|
||||
|
||||
def update(
|
||||
self,
|
||||
|
||||
avg_price: float,
|
||||
size: float,
|
||||
|
||||
) -> None:
|
||||
'''Update graphics and data from average price and size.
|
||||
|
||||
'''
|
||||
self.update_line(avg_price, size)
|
||||
|
||||
self._level_marker.level = avg_price
|
||||
self._level_marker.update() # trigger paint
|
||||
|
||||
# info updates
|
||||
self.info['avg_price'] = avg_price
|
||||
self.info['size'] = size
|
||||
|
||||
# label updates
|
||||
self.size_label.fields['entry_size'] = size
|
||||
self.size_label.render()
|
||||
|
||||
# self.info_label.fields['size'] = size
|
||||
# self.info_label.render()
|
||||
|
|
Loading…
Reference in New Issue