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