Adjust range logic to avoid overlap with labels
By mapping any in view "contents labels" to the range of the ``ViewBox``'s data we can avoid having graphics overlap with labels. Take this approach instead of specifying a min y-range using the std and activate the range compute on resize and mouser scrolling. Also, add y-sticky update for signal plots.bar_select
parent
fc0a03d597
commit
80f191c57d
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
High level Qt chart widgets.
|
High level Qt chart widgets.
|
||||||
"""
|
"""
|
||||||
from typing import Tuple, Dict, Any
|
from typing import Tuple, Dict, Any, Optional
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
|
@ -274,6 +274,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self,
|
self,
|
||||||
# the data view we generate graphics from
|
# the data view we generate graphics from
|
||||||
array: np.ndarray,
|
array: np.ndarray,
|
||||||
|
yrange: Optional[Tuple[float, float]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Configure chart display settings.
|
"""Configure chart display settings.
|
||||||
|
@ -282,12 +283,15 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
background=hcolor('papas_special'),
|
background=hcolor('papas_special'),
|
||||||
# parent=None,
|
# parent=None,
|
||||||
# plotItem=None,
|
# plotItem=None,
|
||||||
|
# useOpenGL=True,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
self._array = array # readonly view of data
|
self._array = array # readonly view of data
|
||||||
self._graphics = {} # registry of underlying graphics
|
self._graphics = {} # registry of underlying graphics
|
||||||
self._labels = {} # registry of underlying graphics
|
self._labels = {} # registry of underlying graphics
|
||||||
self._ysticks = {} # registry of underlying graphics
|
self._ysticks = {} # registry of underlying graphics
|
||||||
|
self._yrange = yrange
|
||||||
|
self._vb = self.plotItem.vb
|
||||||
|
|
||||||
# show only right side axes
|
# show only right side axes
|
||||||
self.hideAxis('left')
|
self.hideAxis('left')
|
||||||
|
@ -301,10 +305,16 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# use cross-hair for cursor
|
# use cross-hair for cursor
|
||||||
self.setCursor(QtCore.Qt.CrossCursor)
|
self.setCursor(QtCore.Qt.CrossCursor)
|
||||||
|
|
||||||
# assign callback for rescaling y-axis automatically
|
# Assign callback for rescaling y-axis automatically
|
||||||
# based on ohlc contents
|
# based on data contents and ``ViewBox`` state.
|
||||||
self.sigXRangeChanged.connect(self._set_yrange)
|
self.sigXRangeChanged.connect(self._set_yrange)
|
||||||
|
|
||||||
|
vb = self._vb
|
||||||
|
# for mouse wheel which doesn't seem to emit XRangeChanged
|
||||||
|
vb.sigRangeChangedManually.connect(self._set_yrange)
|
||||||
|
# for when the splitter(s) are resized
|
||||||
|
vb.sigResized.connect(self._set_yrange)
|
||||||
|
|
||||||
def _update_contents_label(self, index: int) -> None:
|
def _update_contents_label(self, index: int) -> None:
|
||||||
if index > 0 and index < len(self._array):
|
if index > 0 and index < len(self._array):
|
||||||
for name, (label, update) in self._labels.items():
|
for name, (label, update) in self._labels.items():
|
||||||
|
@ -364,9 +374,10 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
def update(index: int) -> None:
|
def update(index: int) -> None:
|
||||||
label.setText(
|
label.setText(
|
||||||
"{name} O:{} H:{} L:{} C:{} V:{}".format(
|
"{name}[{index}] -> O:{} H:{} L:{} C:{} V:{}".format(
|
||||||
*self._array[index].item()[2:],
|
*self._array[index].item()[2:],
|
||||||
name=name,
|
name=name,
|
||||||
|
index=index,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -412,7 +423,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
def update(index: int) -> None:
|
def update(index: int) -> None:
|
||||||
data = self._array[index]
|
data = self._array[index]
|
||||||
label.setText(f"{name}: {index} {data}")
|
label.setText(f"{name} -> {data}")
|
||||||
|
|
||||||
self._labels[name] = (label, update)
|
self._labels[name] = (label, update)
|
||||||
self._update_contents_label(index=-1)
|
self._update_contents_label(index=-1)
|
||||||
|
@ -427,6 +438,8 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# "only update with new items" on the pg.PlotDataItem
|
# "only update with new items" on the pg.PlotDataItem
|
||||||
curve.update_from_array = curve.setData
|
curve.update_from_array = curve.setData
|
||||||
|
|
||||||
|
self._add_sticky(name)
|
||||||
|
|
||||||
return curve
|
return curve
|
||||||
|
|
||||||
def _add_sticky(
|
def _add_sticky(
|
||||||
|
@ -435,7 +448,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# retreive: Callable[None, np.ndarray],
|
# retreive: Callable[None, np.ndarray],
|
||||||
) -> YSticky:
|
) -> YSticky:
|
||||||
# add y-axis "last" value label
|
# add y-axis "last" value label
|
||||||
last = self._ysticks['last'] = YSticky(
|
last = self._ysticks[name] = YSticky(
|
||||||
chart=self,
|
chart=self,
|
||||||
parent=self.getAxis('right'),
|
parent=self.getAxis('right'),
|
||||||
# digits=0,
|
# digits=0,
|
||||||
|
@ -490,33 +503,43 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# likely no data loaded yet
|
# likely no data loaded yet
|
||||||
log.error(f"WTF bars_range = {lbar}:{rbar}")
|
log.error(f"WTF bars_range = {lbar}:{rbar}")
|
||||||
return
|
return
|
||||||
elif lbar < 0:
|
|
||||||
breakpoint()
|
|
||||||
|
|
||||||
# TODO: should probably just have some kinda attr mark
|
# TODO: should probably just have some kinda attr mark
|
||||||
# that determines this behavior based on array type
|
# that determines this behavior based on array type
|
||||||
try:
|
try:
|
||||||
ylow = bars['low'].min()
|
ylow = bars['low'].min()
|
||||||
yhigh = bars['high'].max()
|
yhigh = bars['high'].max()
|
||||||
std = np.std(bars['close'])
|
# std = np.std(bars['close'])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# must be non-ohlc array?
|
# must be non-ohlc array?
|
||||||
ylow = bars.min()
|
ylow = bars.min()
|
||||||
yhigh = bars.max()
|
yhigh = bars.max()
|
||||||
std = np.std(bars)
|
# std = np.std(bars)
|
||||||
|
|
||||||
# view margins: stay within 10% of the "true range"
|
# view margins: stay within 10% of the "true range"
|
||||||
diff = yhigh - ylow
|
diff = yhigh - ylow
|
||||||
ylow = ylow - (diff * 0.1)
|
ylow = ylow - (diff * 0.04)
|
||||||
yhigh = yhigh + (diff * 0.1)
|
yhigh = yhigh + (diff * 0.01)
|
||||||
|
|
||||||
|
# compute contents label "height" in view terms
|
||||||
|
if self._labels:
|
||||||
|
label = self._labels[self.name][0]
|
||||||
|
rect = label.itemRect()
|
||||||
|
tl, br = rect.topLeft(), rect.bottomRight()
|
||||||
|
vb = self.plotItem.vb
|
||||||
|
top, bottom = (vb.mapToView(tl).y(), vb.mapToView(br).y())
|
||||||
|
label_h = top - bottom
|
||||||
|
# print(f'label height {self.name}: {label_h}')
|
||||||
|
else:
|
||||||
|
label_h = 0
|
||||||
|
|
||||||
chart = self
|
chart = self
|
||||||
chart.setLimits(
|
chart.setLimits(
|
||||||
yMin=ylow,
|
yMin=ylow,
|
||||||
yMax=yhigh,
|
yMax=yhigh + label_h,
|
||||||
minYRange=std
|
# minYRange=std
|
||||||
)
|
)
|
||||||
chart.setYRange(ylow, yhigh)
|
chart.setYRange(ylow, yhigh + label_h)
|
||||||
|
|
||||||
def enterEvent(self, ev): # noqa
|
def enterEvent(self, ev): # noqa
|
||||||
# pg.PlotWidget.enterEvent(self, ev)
|
# pg.PlotWidget.enterEvent(self, ev)
|
||||||
|
@ -577,7 +600,10 @@ async def add_new_bars(delay_s, linked_charts):
|
||||||
return new_array
|
return new_array
|
||||||
|
|
||||||
# add new increment/bar
|
# add new increment/bar
|
||||||
|
start = time.time()
|
||||||
ohlc = price_chart._array = incr_ohlc_array(ohlc)
|
ohlc = price_chart._array = incr_ohlc_array(ohlc)
|
||||||
|
diff = time.time() - start
|
||||||
|
print(f'array append took {diff}')
|
||||||
|
|
||||||
# TODO: generalize this increment logic
|
# TODO: generalize this increment logic
|
||||||
for name, chart in linked_charts.subplots.items():
|
for name, chart in linked_charts.subplots.items():
|
||||||
|
@ -660,7 +686,7 @@ async def _async_main(
|
||||||
n.start_soon(
|
n.start_soon(
|
||||||
chart_from_fsp,
|
chart_from_fsp,
|
||||||
linked_charts,
|
linked_charts,
|
||||||
fsp.latency,
|
'rsi',
|
||||||
sym,
|
sym,
|
||||||
bars,
|
bars,
|
||||||
brokermod,
|
brokermod,
|
||||||
|
@ -668,8 +694,10 @@ async def _async_main(
|
||||||
)
|
)
|
||||||
|
|
||||||
# update last price sticky
|
# update last price sticky
|
||||||
last = chart._ysticks['last']
|
last_price_sticky = chart._ysticks[chart.name]
|
||||||
last.update_from_data(*chart._array[-1][['index', 'close']])
|
last_price_sticky.update_from_data(
|
||||||
|
*chart._array[-1][['index', 'close']]
|
||||||
|
)
|
||||||
|
|
||||||
# graphics update loop
|
# graphics update loop
|
||||||
|
|
||||||
|
@ -711,15 +739,14 @@ async def _async_main(
|
||||||
chart._array,
|
chart._array,
|
||||||
)
|
)
|
||||||
# update sticky(s)
|
# update sticky(s)
|
||||||
last = chart._ysticks['last']
|
last_price_sticky.update_from_data(
|
||||||
last.update_from_data(
|
|
||||||
*chart._array[-1][['index', 'close']])
|
*chart._array[-1][['index', 'close']])
|
||||||
chart._set_yrange()
|
chart._set_yrange()
|
||||||
|
|
||||||
|
|
||||||
async def chart_from_fsp(
|
async def chart_from_fsp(
|
||||||
linked_charts,
|
linked_charts,
|
||||||
fsp_func,
|
func_name,
|
||||||
sym,
|
sym,
|
||||||
bars,
|
bars,
|
||||||
brokermod,
|
brokermod,
|
||||||
|
@ -729,8 +756,6 @@ async def chart_from_fsp(
|
||||||
|
|
||||||
Pass target entrypoint and historical data.
|
Pass target entrypoint and historical data.
|
||||||
"""
|
"""
|
||||||
func_name = fsp_func.__name__
|
|
||||||
|
|
||||||
async with tractor.open_nursery() as n:
|
async with tractor.open_nursery() as n:
|
||||||
portal = await n.run_in_actor(
|
portal = await n.run_in_actor(
|
||||||
f'fsp.{func_name}', # name as title of sub-chart
|
f'fsp.{func_name}', # name as title of sub-chart
|
||||||
|
@ -749,7 +774,7 @@ async def chart_from_fsp(
|
||||||
stream = await portal.result()
|
stream = await portal.result()
|
||||||
|
|
||||||
# receive processed historical data-array as first message
|
# receive processed historical data-array as first message
|
||||||
history: np.ndarray = (await stream.__anext__())
|
history = (await stream.__anext__())
|
||||||
|
|
||||||
# TODO: enforce type checking here
|
# TODO: enforce type checking here
|
||||||
newbars = np.array(history)
|
newbars = np.array(history)
|
||||||
|
@ -768,9 +793,15 @@ async def chart_from_fsp(
|
||||||
np.full(abs(diff), data[-1], dtype=data.dtype)
|
np.full(abs(diff), data[-1], dtype=data.dtype)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
value = chart._array[-1]
|
||||||
|
last_val_sticky = chart._ysticks[chart.name]
|
||||||
|
last_val_sticky.update_from_data(-1, value)
|
||||||
|
|
||||||
# update chart graphics
|
# update chart graphics
|
||||||
async for value in stream:
|
async for value in stream:
|
||||||
chart._array[-1] = value
|
chart._array[-1] = value
|
||||||
|
last_val_sticky.update_from_data(-1, value)
|
||||||
|
chart._set_yrange()
|
||||||
chart.update_from_array(chart.name, chart._array)
|
chart.update_from_array(chart.name, chart._array)
|
||||||
chart._set_yrange()
|
chart._set_yrange()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue