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,6 +169,57 @@ class CrossHairItem(pg.GraphicsObject):
return self.parent.boundingRect() return self.parent.boundingRect()
def bars_from_ohlc(
data: np.ndarray,
start: int = 0,
w: float = 0.4,
) -> np.ndarray:
lines = np.empty_like(data, shape=(data.shape[0]*3,), dtype=object)
for i, q in enumerate(data[start:], start=start):
low = q['low']
high = q['high']
index = q['index']
# high - low line
if low != high:
hl = QLineF(index, low, index, high)
else:
# XXX: if we don't do it renders a weird rectangle?
# see below too for handling this later...
hl = QLineF(low, low, low, low)
hl._flat = True
# open line
o = QLineF(index - w, q['open'], index, q['open'])
# close line
c = QLineF(index + w, q['close'], index, q['close'])
# indexing here is as per the below comments
lines[3*i:3*i+3] = (hl, o, c)
# if not _tina_mode: # piker mode
# else _tina_mode:
# self.lines = lines = np.concatenate(
# [high_to_low, open_sticks, close_sticks])
# use traditional up/down green/red coloring
# long_bars = np.resize(Quotes.close > Quotes.open, len(lines))
# short_bars = np.resize(
# Quotes.close < Quotes.open, len(lines))
# ups = lines[long_bars]
# downs = lines[short_bars]
# # draw "up" bars
# p.setPen(self.bull_brush)
# p.drawLines(*ups)
# # draw "down" bars
# p.setPen(self.bear_brush)
# p.drawLines(*downs)
return lines
class BarItems(pg.GraphicsObject): class BarItems(pg.GraphicsObject):
"""Price range bars graphics rendered from a OHLC sequence. """Price range bars graphics rendered from a OHLC sequence.
""" """
@ -185,8 +236,22 @@ class BarItems(pg.GraphicsObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.picture = QtGui.QPicture() 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 # lines container
self.lines: np.ndarray = None 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? # TODO: can we be faster just dropping this?
@contextmanager @contextmanager
@ -201,57 +266,21 @@ class BarItems(pg.GraphicsObject):
def draw_from_data( def draw_from_data(
self, self,
data: np.recarray, data: np.recarray,
start: int = 0,
): ):
"""Draw OHLC datum graphics from a ``np.recarray``. """Draw OHLC datum graphics from a ``np.recarray``.
""" """
# XXX: not sure this actually needs to be an array other lines = bars_from_ohlc(data, start=start, w=self.w)
# then for the old tina mode calcs for up/down bars below?
self.lines = lines = np.empty_like( # save graphics for later reference and keep track
data, shape=(data.shape[0]*3,), dtype=object) # of current internal "last index"
index = len(lines)
self.lines[:index] = lines
self.index = index
with self.painter() as p: with self.painter() as p:
for i, q in enumerate(data):
low = q['low']
high = q['high']
index = q['index']
# high - low line
if low != high:
hl = QLineF(index, low, index, high)
else:
# XXX: if we don't do it renders a weird rectangle?
# see below too for handling this later...
hl = QLineF(low, low, low, low)
hl._flat = True
# open line
o = QLineF(index - self.w, q['open'], index, q['open'])
# close line
c = QLineF(index + self.w, q['close'], index, q['close'])
# indexing here is as per the below comments
lines[3*i:3*i+3] = (hl, o, c)
p.setPen(self.bull_pen) p.setPen(self.bull_pen)
p.drawLines(*lines) p.drawLines(*self.lines[:index])
# if not _tina_mode: # piker mode
# else _tina_mode:
# self.lines = lines = np.concatenate(
# [high_to_low, open_sticks, close_sticks])
# use traditional up/down green/red coloring
# long_bars = np.resize(Quotes.close > Quotes.open, len(lines))
# short_bars = np.resize(
# Quotes.close < Quotes.open, len(lines))
# ups = lines[long_bars]
# downs = lines[short_bars]
# # draw "up" bars
# p.setPen(self.bull_brush)
# p.drawLines(*ups)
# # draw "down" bars
# p.setPen(self.bear_brush)
# p.drawLines(*downs)
def update_from_array( def update_from_array(
self, self,
@ -266,39 +295,57 @@ 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.
""" """
# do we really need to verify the entire past data set? index = self.index
# last = array['close'][-1] length = len(array)
# index, time, open, high, low, close, volume idata = int(index/3) - 1
index, time, _, _, _, close, _ = array[-1] extra = length - idata
last = close if extra > 0:
body, larm, rarm = self.lines[-3:] # generate new graphics to match provided array
new = array[-extra:]
lines = bars_from_ohlc(new, w=self.w)
# XXX: is there a faster way to modify this? added = len(new) * 3
# update right arm assert len(lines) == added
rarm.setLine(rarm.x1(), last, rarm.x2(), last)
# update body self.lines[index:index + len(lines)] = lines
high = body.y2()
low = body.y1()
if last < low:
low = last
if last > high: self.index += len(lines)
high = last
if getattr(body, '_flat', None) and low != high: else: # current bar update
# if the bar was flat it likely does not have # do we really need to verify the entire past data set?
# the index set correctly due to a rendering bug # index, time, open, high, low, close, volume
# see above i, time, _, _, _, close, _ = array[-1]
body.setLine(index, low, index, high) last = close
body._flat = False body, larm, rarm = self.lines[index-3:index]
else: if not rarm:
body.setLine(body.x1(), low, body.x2(), high) breakpoint()
# XXX: is there a faster way to modify this?
# update right arm
rarm.setLine(rarm.x1(), last, rarm.x2(), last)
# update body
high = body.y2()
low = body.y1()
if last < low:
low = last
if last > high:
high = last
if getattr(body, '_flat', None) and low != high:
# if the bar was flat it likely does not have
# the index set correctly due to a rendering bug
# see above
body.setLine(i, low, i, high)
body._flat = False
else:
body.setLine(body.x1(), low, body.x2(), high)
# 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