Fix contents labels issues
Lookup overlay contents from the OHLC struct array (for now / to make things work) and fix anchoring logic with better offsets to keep contents labels super tight to the edge of the view box. Unfortunately, had to hack the label-height-calc thing for avoiding overlap of graphics with the label; haven't found a better solution yet and pyqtgraph seems to require more rabbit holing to figure out something better. Slap in some inf lines for over[sold/bought] rsi conditions thresholding.bar_select
parent
851104dd31
commit
c57f678295
|
@ -13,13 +13,14 @@ from ._axes import (
|
||||||
DynamicDateAxis,
|
DynamicDateAxis,
|
||||||
PriceAxis,
|
PriceAxis,
|
||||||
)
|
)
|
||||||
from ._graphics import CrossHair, BarItems
|
from ._graphics import CrossHair, BarItems, h_line
|
||||||
from ._axes import YSticky
|
from ._axes import YSticky
|
||||||
from ._style import (
|
from ._style import (
|
||||||
_xaxis_at, _min_points_to_show, hcolor,
|
_xaxis_at, _min_points_to_show, hcolor,
|
||||||
CHART_MARGINS,
|
CHART_MARGINS,
|
||||||
_bars_from_right_in_follow_mode,
|
_bars_from_right_in_follow_mode,
|
||||||
_bars_to_left_in_follow_mode,
|
_bars_to_left_in_follow_mode,
|
||||||
|
# _font,
|
||||||
)
|
)
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
|
@ -295,6 +296,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
background=hcolor('papas_special'),
|
background=hcolor('papas_special'),
|
||||||
# parent=None,
|
# parent=None,
|
||||||
# plotItem=None,
|
# plotItem=None,
|
||||||
|
# antialias=True,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
self._array = array # readonly view of data
|
self._array = array # readonly view of data
|
||||||
|
@ -382,10 +384,14 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# Ogi says: "use ..."
|
# Ogi says: "use ..."
|
||||||
label = pg.LabelItem(
|
label = pg.LabelItem(
|
||||||
justify='left',
|
justify='left',
|
||||||
size='4pt',
|
size='6px',
|
||||||
)
|
)
|
||||||
|
label.setParentItem(self._vb)
|
||||||
self.scene().addItem(label)
|
self.scene().addItem(label)
|
||||||
|
|
||||||
|
# keep close to top
|
||||||
|
label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, -4))
|
||||||
|
|
||||||
def update(index: int) -> None:
|
def update(index: int) -> None:
|
||||||
label.setText(
|
label.setText(
|
||||||
"{name}[{index}] -> O:{} H:{} L:{} C:{} V:{}".format(
|
"{name}[{index}] -> O:{} H:{} L:{} C:{} V:{}".format(
|
||||||
|
@ -427,7 +433,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
curve = pg.PlotDataItem(
|
curve = pg.PlotDataItem(
|
||||||
data,
|
data,
|
||||||
antialias=True,
|
# antialias=True,
|
||||||
name=name,
|
name=name,
|
||||||
# TODO: see how this handles with custom ohlcv bars graphics
|
# TODO: see how this handles with custom ohlcv bars graphics
|
||||||
clipToView=True,
|
clipToView=True,
|
||||||
|
@ -443,20 +449,32 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
justify='left',
|
justify='left',
|
||||||
size='6px',
|
size='6px',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# anchor to the viewbox
|
||||||
label.setParentItem(self._vb)
|
label.setParentItem(self._vb)
|
||||||
# label.setParentItem(self.getPlotItem())
|
# label.setParentItem(self.getPlotItem())
|
||||||
|
|
||||||
if overlay:
|
if overlay:
|
||||||
# position bottom left if an overlay
|
# position bottom left if an overlay
|
||||||
label.anchor(itemPos=(0, 1), parentPos=(0, 1), offset=(0, 14))
|
label.anchor(itemPos=(1, 1), parentPos=(1, 1), offset=(0, 3))
|
||||||
self._overlays[name] = curve
|
self._overlays[name] = curve
|
||||||
|
|
||||||
label.show()
|
def update(index: int) -> None:
|
||||||
self.scene().addItem(label)
|
data = self._array[index][name]
|
||||||
|
label.setText(f"{name}: {data:.2f}")
|
||||||
|
else:
|
||||||
|
label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, -4))
|
||||||
|
|
||||||
def update(index: int) -> None:
|
def update(index: int) -> None:
|
||||||
data = self._array[index]
|
data = self._array[index]
|
||||||
label.setText(f"{name} -> {data}")
|
label.setText(f"{name}: {data:.2f}")
|
||||||
|
|
||||||
|
# def update(index: int) -> None:
|
||||||
|
# data = self._array[index]
|
||||||
|
# label.setText(f"{name} -> {data:.2f}")
|
||||||
|
|
||||||
|
label.show()
|
||||||
|
self.scene().addItem(label)
|
||||||
|
|
||||||
self._labels[name] = (label, update)
|
self._labels[name] = (label, update)
|
||||||
self._update_contents_label(len(data) - 1)
|
self._update_contents_label(len(data) - 1)
|
||||||
|
@ -574,26 +592,32 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
# view margins: stay within a % of the "true range"
|
# view margins: stay within a % of the "true range"
|
||||||
diff = yhigh - ylow
|
diff = yhigh - ylow
|
||||||
ylow = ylow - (diff * 0.08)
|
ylow = ylow - (diff * 0.04)
|
||||||
yhigh = yhigh + (diff * 0.01)
|
# yhigh = yhigh + (diff * 0.01)
|
||||||
|
|
||||||
# compute contents label "height" in view terms
|
# compute contents label "height" in view terms
|
||||||
# to avoid having data "contents" overlap with them
|
# to avoid having data "contents" overlap with them
|
||||||
if self._labels:
|
if self._labels:
|
||||||
label = self._labels[self.name][0]
|
label = self._labels[self.name][0]
|
||||||
|
|
||||||
rect = label.itemRect()
|
rect = label.itemRect()
|
||||||
tl, br = rect.topLeft(), rect.bottomRight()
|
tl, br = rect.topLeft(), rect.bottomRight()
|
||||||
vb = self.plotItem.vb
|
vb = self.plotItem.vb
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# on startup labels might not yet be rendered
|
# on startup labels might not yet be rendered
|
||||||
top, bottom = (vb.mapToView(tl).y(), vb.mapToView(br).y())
|
top, bottom = (vb.mapToView(tl).y(), vb.mapToView(br).y())
|
||||||
label_h = top - bottom
|
|
||||||
|
# XXX: hack, how do we compute exactly?
|
||||||
|
label_h = (top - bottom) * 0.42
|
||||||
|
|
||||||
except np.linalg.LinAlgError:
|
except np.linalg.LinAlgError:
|
||||||
label_h = 0
|
label_h = 0
|
||||||
# print(f'label height {self.name}: {label_h}')
|
|
||||||
else:
|
else:
|
||||||
label_h = 0
|
label_h = 0
|
||||||
|
|
||||||
|
# print(f'label height {self.name}: {label_h}')
|
||||||
|
|
||||||
if label_h > yhigh - ylow:
|
if label_h > yhigh - ylow:
|
||||||
label_h = 0
|
label_h = 0
|
||||||
|
|
||||||
|
@ -731,13 +755,16 @@ async def chart_from_quotes(
|
||||||
for sym, quote in quotes.items():
|
for sym, quote in quotes.items():
|
||||||
for tick in iterticks(quote, type='trade'):
|
for tick in iterticks(quote, type='trade'):
|
||||||
array = ohlcv.array
|
array = ohlcv.array
|
||||||
|
|
||||||
|
# update price sticky(s)
|
||||||
|
last = array[-1]
|
||||||
|
last_price_sticky.update_from_data(*last[['index', 'close']])
|
||||||
|
|
||||||
|
# update price bar
|
||||||
chart.update_from_array(
|
chart.update_from_array(
|
||||||
chart.name,
|
chart.name,
|
||||||
array,
|
array,
|
||||||
)
|
)
|
||||||
# update sticky(s)
|
|
||||||
last = array[-1]
|
|
||||||
last_price_sticky.update_from_data(*last[['index', 'close']])
|
|
||||||
|
|
||||||
# TODO: we need a streaming minmax algorithm here to
|
# TODO: we need a streaming minmax algorithm here to
|
||||||
brange, mx, mn = maxmin()
|
brange, mx, mn = maxmin()
|
||||||
|
@ -762,7 +789,7 @@ async def chart_from_quotes(
|
||||||
|
|
||||||
async def chart_from_fsp(
|
async def chart_from_fsp(
|
||||||
linked_charts,
|
linked_charts,
|
||||||
func_name,
|
fsp_func_name,
|
||||||
sym,
|
sym,
|
||||||
src_shm,
|
src_shm,
|
||||||
brokermod,
|
brokermod,
|
||||||
|
@ -772,10 +799,13 @@ async def chart_from_fsp(
|
||||||
|
|
||||||
Pass target entrypoint and historical data.
|
Pass target entrypoint and historical data.
|
||||||
"""
|
"""
|
||||||
name = f'fsp.{func_name}'
|
name = f'fsp.{fsp_func_name}'
|
||||||
|
|
||||||
# TODO: load function here and introspect
|
# TODO: load function here and introspect
|
||||||
# return stream type(s)
|
# return stream type(s)
|
||||||
fsp_dtype = np.dtype([('index', int), (func_name, float)])
|
|
||||||
|
# TODO: should `index` be a required internal field?
|
||||||
|
fsp_dtype = np.dtype([('index', int), (fsp_func_name, float)])
|
||||||
|
|
||||||
async with tractor.open_nursery() as n:
|
async with tractor.open_nursery() as n:
|
||||||
key = f'{sym}.' + name
|
key = f'{sym}.' + name
|
||||||
|
@ -786,13 +816,16 @@ async def chart_from_fsp(
|
||||||
dtype=fsp_dtype,
|
dtype=fsp_dtype,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# XXX: fsp may have been opened by a duplicate chart. Error for
|
# XXX: fsp may have been opened by a duplicate chart. Error for
|
||||||
# now until we figure out how to wrap fsps as "feeds".
|
# now until we figure out how to wrap fsps as "feeds".
|
||||||
assert opened, f"A chart for {key} likely already exists?"
|
assert opened, f"A chart for {key} likely already exists?"
|
||||||
|
|
||||||
# start fsp sub-actor
|
# start fsp sub-actor
|
||||||
portal = await n.run_in_actor(
|
portal = await n.run_in_actor(
|
||||||
name, # name as title of sub-chart
|
|
||||||
|
# name as title of sub-chart
|
||||||
|
name,
|
||||||
|
|
||||||
# subactor entrypoint
|
# subactor entrypoint
|
||||||
fsp.cascade,
|
fsp.cascade,
|
||||||
|
@ -800,7 +833,7 @@ async def chart_from_fsp(
|
||||||
src_shm_token=src_shm.token,
|
src_shm_token=src_shm.token,
|
||||||
dst_shm_token=shm.token,
|
dst_shm_token=shm.token,
|
||||||
symbol=sym,
|
symbol=sym,
|
||||||
fsp_func_name=func_name,
|
fsp_func_name=fsp_func_name,
|
||||||
|
|
||||||
# tractor config
|
# tractor config
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
|
@ -813,8 +846,8 @@ async def chart_from_fsp(
|
||||||
_ = await stream.receive()
|
_ = await stream.receive()
|
||||||
|
|
||||||
chart = linked_charts.add_plot(
|
chart = linked_charts.add_plot(
|
||||||
name=func_name,
|
name=fsp_func_name,
|
||||||
array=shm.array,
|
array=shm.array[fsp_func_name],
|
||||||
|
|
||||||
# settings passed down to ``ChartPlotWidget``
|
# settings passed down to ``ChartPlotWidget``
|
||||||
static_yrange=(0, 100),
|
static_yrange=(0, 100),
|
||||||
|
@ -823,21 +856,39 @@ async def chart_from_fsp(
|
||||||
# display contents labels asap
|
# display contents labels asap
|
||||||
chart._update_contents_label(len(shm.array) - 1)
|
chart._update_contents_label(len(shm.array) - 1)
|
||||||
|
|
||||||
array = shm.array[func_name]
|
array = shm.array
|
||||||
value = array[-1]
|
value = array[fsp_func_name][-1]
|
||||||
|
|
||||||
last_val_sticky = chart._ysticks[chart.name]
|
last_val_sticky = chart._ysticks[chart.name]
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, array)
|
|
||||||
|
chart.update_from_array(chart.name, array[fsp_func_name])
|
||||||
|
|
||||||
|
# TODO: figure out if we can roll our own `FillToThreshold` to
|
||||||
|
# get brush filled polygons for OS/OB conditions.
|
||||||
|
# ``pg.FillBetweenItems`` seems to be one technique using
|
||||||
|
# generic fills between curve types while ``PlotCurveItem`` has
|
||||||
|
# logic inside ``.paint()`` for ``self.opts['fillLevel']`` which
|
||||||
|
# might be the best solution?
|
||||||
|
# graphics = chart.update_from_array(chart.name, array[fsp_func_name])
|
||||||
|
# graphics.curve.setBrush(50, 50, 200, 100)
|
||||||
|
# graphics.curve.setFillLevel(50)
|
||||||
|
|
||||||
|
# add moveable over-[sold/bought] lines
|
||||||
|
chart.plotItem.addItem(h_line(30))
|
||||||
|
chart.plotItem.addItem(h_line(70))
|
||||||
|
|
||||||
chart._shm = shm
|
chart._shm = shm
|
||||||
chart._set_yrange()
|
chart._set_yrange()
|
||||||
|
|
||||||
# update chart graphics
|
# update chart graphics
|
||||||
async for value in stream:
|
async for value in stream:
|
||||||
array = shm.array[func_name]
|
# p = pg.debug.Profiler(disabled=False, delayed=False)
|
||||||
value = array[-1]
|
array = shm.array
|
||||||
|
value = array[-1][fsp_func_name]
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, array)
|
chart.update_from_array(chart.name, array[fsp_func_name])
|
||||||
|
# p('rendered rsi datum')
|
||||||
|
|
||||||
|
|
||||||
async def check_for_new_bars(feed, ohlcv, linked_charts):
|
async def check_for_new_bars(feed, ohlcv, linked_charts):
|
||||||
|
|
Loading…
Reference in New Issue