Move bar generation into func; support bar appends

There's really nothing coupling it to the graphics class (which frankly
also seems like it doesn't need to be a class.. Qt).

Add support to `.update_from_array()` for diffing with the input array
and creating additional bar-lines where necessary. Note, there are still
issues with the "correctness" here in terms of bucketing open/close
values in the time frame / bar range. Also, this jamming of each bar's 3
lines into a homogeneous array seems like it could be better done with
struct arrays and avoid all this "index + 3" stuff.
its_happening
Tyler Goodlet 2020-07-08 15:06:39 -04:00
parent 4aa526f400
commit 7bc49eac9f
1 changed files with 119 additions and 72 deletions

View File

@ -169,48 +169,14 @@ class CrossHairItem(pg.GraphicsObject):
return self.parent.boundingRect() return self.parent.boundingRect()
class BarItems(pg.GraphicsObject): def bars_from_ohlc(
"""Price range bars graphics rendered from a OHLC sequence. data: np.ndarray,
""" start: int = 0,
sigPlotChanged = QtCore.Signal(object) w: float = 0.4,
) -> np.ndarray:
lines = np.empty_like(data, shape=(data.shape[0]*3,), dtype=object)
# 0.5 is no overlap between arms, 1.0 is full overlap for i, q in enumerate(data[start:], start=start):
w: float = 0.4
bull_pen = pg.mkPen('#808080')
# XXX: tina mode, see below
# bull_brush = pg.mkPen('#00cc00')
# bear_brush = pg.mkPen('#fa0000')
def __init__(self):
super().__init__()
self.picture = QtGui.QPicture()
# lines container
self.lines: np.ndarray = None
# TODO: can we be faster just dropping this?
@contextmanager
def painter(self):
# pre-computing a QPicture object allows paint() to run much
# more quickly, rather than re-drawing the shapes every time.
p = QtGui.QPainter(self.picture)
yield p
p.end()
@timeit
def draw_from_data(
self,
data: np.recarray,
):
"""Draw OHLC datum graphics from a ``np.recarray``.
"""
# XXX: not sure this actually needs to be an array other
# then for the old tina mode calcs for up/down bars below?
self.lines = lines = np.empty_like(
data, shape=(data.shape[0]*3,), dtype=object)
with self.painter() as p:
for i, q in enumerate(data):
low = q['low'] low = q['low']
high = q['high'] high = q['high']
index = q['index'] index = q['index']
@ -224,15 +190,13 @@ class BarItems(pg.GraphicsObject):
hl = QLineF(low, low, low, low) hl = QLineF(low, low, low, low)
hl._flat = True hl._flat = True
# open line # open line
o = QLineF(index - self.w, q['open'], index, q['open']) o = QLineF(index - w, q['open'], index, q['open'])
# close line # close line
c = QLineF(index + self.w, q['close'], index, q['close']) c = QLineF(index + w, q['close'], index, q['close'])
# indexing here is as per the below comments # indexing here is as per the below comments
lines[3*i:3*i+3] = (hl, o, c) lines[3*i:3*i+3] = (hl, o, c)
p.setPen(self.bull_pen)
p.drawLines(*lines)
# if not _tina_mode: # piker mode # if not _tina_mode: # piker mode
# else _tina_mode: # else _tina_mode:
# self.lines = lines = np.concatenate( # self.lines = lines = np.concatenate(
@ -253,6 +217,71 @@ class BarItems(pg.GraphicsObject):
# p.setPen(self.bear_brush) # p.setPen(self.bear_brush)
# p.drawLines(*downs) # p.drawLines(*downs)
return lines
class BarItems(pg.GraphicsObject):
"""Price range bars graphics rendered from a OHLC sequence.
"""
sigPlotChanged = QtCore.Signal(object)
# 0.5 is no overlap between arms, 1.0 is full overlap
w: float = 0.4
bull_pen = pg.mkPen('#808080')
# XXX: tina mode, see below
# bull_brush = pg.mkPen('#00cc00')
# bear_brush = pg.mkPen('#fa0000')
def __init__(self):
super().__init__()
self.picture = QtGui.QPicture()
# XXX: not sure this actually needs to be an array other
# then for the old tina mode calcs for up/down bars below?
# lines container
self.lines: np.ndarray = np.empty_like(
[], shape=(int(50e3),), dtype=object)
self._index = 0
def _set_index(self, val):
# breakpoint()
self._index = val
def _get_index(self):
return self._index
index = property(_get_index, _set_index)
# TODO: can we be faster just dropping this?
@contextmanager
def painter(self):
# pre-computing a QPicture object allows paint() to run much
# more quickly, rather than re-drawing the shapes every time.
p = QtGui.QPainter(self.picture)
yield p
p.end()
@timeit
def draw_from_data(
self,
data: np.recarray,
start: int = 0,
):
"""Draw OHLC datum graphics from a ``np.recarray``.
"""
lines = bars_from_ohlc(data, start=start, w=self.w)
# save graphics for later reference and keep track
# of current internal "last index"
index = len(lines)
self.lines[:index] = lines
self.index = index
with self.painter() as p:
p.setPen(self.bull_pen)
p.drawLines(*self.lines[:index])
def update_from_array( def update_from_array(
self, self,
array: np.ndarray, array: np.ndarray,
@ -266,12 +295,30 @@ class BarItems(pg.GraphicsObject):
assuming the prior graphics havent changed (OHLC history rarely assuming the prior graphics havent changed (OHLC history rarely
does) so this "should" be simpler and faster. does) so this "should" be simpler and faster.
""" """
index = self.index
length = len(array)
idata = int(index/3) - 1
extra = length - idata
if extra > 0:
# generate new graphics to match provided array
new = array[-extra:]
lines = bars_from_ohlc(new, w=self.w)
added = len(new) * 3
assert len(lines) == added
self.lines[index:index + len(lines)] = lines
self.index += len(lines)
else: # current bar update
# do we really need to verify the entire past data set? # do we really need to verify the entire past data set?
# last = array['close'][-1]
# index, time, open, high, low, close, volume # index, time, open, high, low, close, volume
index, time, _, _, _, close, _ = array[-1] i, time, _, _, _, close, _ = array[-1]
last = close last = close
body, larm, rarm = self.lines[-3:] body, larm, rarm = self.lines[index-3:index]
if not rarm:
breakpoint()
# XXX: is there a faster way to modify this? # XXX: is there a faster way to modify this?
# update right arm # update right arm
@ -290,7 +337,7 @@ class BarItems(pg.GraphicsObject):
# if the bar was flat it likely does not have # if the bar was flat it likely does not have
# the index set correctly due to a rendering bug # the index set correctly due to a rendering bug
# see above # see above
body.setLine(index, low, index, high) body.setLine(i, low, i, high)
body._flat = False body._flat = False
else: else:
body.setLine(body.x1(), low, body.x2(), high) body.setLine(body.x1(), low, body.x2(), high)
@ -298,7 +345,7 @@ class BarItems(pg.GraphicsObject):
# draw the pic # draw the pic
with self.painter() as p: with self.painter() as p:
p.setPen(self.bull_pen) p.setPen(self.bull_pen)
p.drawLines(*self.lines) p.drawLines(*self.lines[:index])
# trigger re-render # trigger re-render
self.update() self.update()
@ -319,7 +366,7 @@ class BarItems(pg.GraphicsObject):
return QtCore.QRectF(self.picture.boundingRect()) return QtCore.QRectF(self.picture.boundingRect())
# XXX: when we get back to enabling tina mode # XXX: when we get back to enabling tina mode for xb
# class CandlestickItems(BarItems): # class CandlestickItems(BarItems):
# w2 = 0.7 # w2 = 0.7