From f01db35c4031b32a0720bae70b7a6e538a9dfa60 Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:30:56 +0200 Subject: [PATCH 01/18] Add Arrow functions with direction ("buy" or "sell"), color and width parameters --- finplot/__init__.py | 59 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 4e85ee3..f2ca164 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -50,6 +50,10 @@ cross_hair_color = '#0007' draw_line_color = '#000' draw_done_color = '#555' +arrow_bull_color = '#20FF20' +arrow_bull_outline_color = '#222222' +arrow_bear_color = '#f7a9a7' +arrow_bear_outline_color = '#f7a9a7' significant_decimals = 8 significant_eps = 1e-8 max_zoom_points = 20 # number of visible candles when maximum zoomed in @@ -634,6 +638,39 @@ def addScaleHandle(self, *args, **kwargs): if self.resizable: super().addScaleHandle(*args, **kwargs) +class FinArrow(pg.ArrowItem): + def __init__(self, ax, direction, brushColor='', brushWidth=1, penColor='', penWidth=1, *args, **kwargs): + + if direction.lower() == "buy": + if brushColor == '': + brushColor = arrow_bull_color + if penColor == '': + penColor = arrow_bull_outline_color + kwargs['angle']=90 + kwargs['tipAngle']=30 + kwargs['baseAngle']=20 + elif direction.lower() == "sell": + if brushColor == '': + brushColor = arrow_bear_color + if penColor == '': + penColor = arrow_bear_outline_color + kwargs['angle']=-90 + kwargs['tipAngle']=30 + kwargs['baseAngle']=20 + + kwargs['headLen']=10 + kwargs['tailLen']=5 + kwargs['tailWidth']=10 + + kwargs['pen']={'color': penColor, 'width': penWidth} + + brush = pg.mkBrush(brushColor) + brush.width = brushWidth + kwargs['brush'] = brush + + self.ax = ax + + super().__init__(*args, **kwargs) class FinViewBox(pg.ViewBox): def __init__(self, win, init_steps=300, yscale=YScale('linear', 1), v_zoom_scale=1, *args, **kwargs): @@ -1199,6 +1236,15 @@ def generate_picture(self, boundingRect): p.drawLine(QtCore.QPointF(t, y), QtCore.QPointF(t+f*self.draw_poc, y)) +class Arrow(): + def __init__(self, buy, color, anchor ): + self.color = color + self.text_items = {} + self.anchor = anchor + self.show = False + + + class ScatterLabelItem(FinPlotItem): def __init__(self, ax, datasrc, color, anchor): @@ -1613,6 +1659,18 @@ def add_band(y0, y1, color=band_color, ax=None): ax.addItem(lr) return lr +def add_arrow(pos, direction="buy", arrow_color='', arrow_brush_width=1, arrow_outline_color='', arrow_outline_width=1, interactive=False, ax=None): + ax = _create_plot(ax=ax, maximize=False) + arrow = FinArrow(ax, direction, arrow_color, arrow_brush_width, arrow_outline_color, arrow_outline_width) + x = pos[0] + if ax.vb.datasrc is not None: + x = _pdtime2index(ax, pd.Series([pos[0]]))[0] + y = ax.vb.yscale.invxform(pos[1]) + arrow.setPos(x, y) + arrow.setZValue(50) + arrow.ax = ax + ax.addItem(arrow, ignoreBounds=True) + return arrow def add_rect(p0, p1, color=band_color, interactive=False, ax=None): ax = _create_plot(ax=ax, maximize=False) @@ -1629,7 +1687,6 @@ def add_rect(p0, p1, color=band_color, interactive=False, ax=None): ax.addItem(rect) return rect - def add_line(p0, p1, color=draw_line_color, width=1, style=None, interactive=False, ax=None): ax = _create_plot(ax=ax, maximize=False) used_color = _get_color(ax, style, color) From 0aef8c39986ecfd77bc567fb3850021ad67df758 Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 15 Oct 2021 11:37:32 +0200 Subject: [PATCH 02/18] Adjust arrow tail & head width --- finplot/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index f2ca164..6f855e0 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -658,9 +658,10 @@ def __init__(self, ax, direction, brushColor='', brushWidth=1, penColor='', penW kwargs['tipAngle']=30 kwargs['baseAngle']=20 - kwargs['headLen']=10 - kwargs['tailLen']=5 - kwargs['tailWidth']=10 + kwargs['headLen']=8 + kwargs['headWidth']=8 + kwargs['tailLen']=6 + kwargs['tailWidth']=5 kwargs['pen']={'color': penColor, 'width': penWidth} From 54762f0345188620223efb3e98098a72e7ed6ea4 Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 15 Oct 2021 11:37:58 +0200 Subject: [PATCH 03/18] Exemple with arrow --- finplot/examples/line.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/finplot/examples/line.py b/finplot/examples/line.py index 5777a3a..a4a0c04 100644 --- a/finplot/examples/line.py +++ b/finplot/examples/line.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import sys +sys.path.append('D:/perso/trading/anaconda3/finplot') import finplot as fplt import numpy as np import pandas as pd @@ -7,12 +9,17 @@ dates = pd.date_range('01:00', '01:00:01.200', freq='1ms') prices = pd.Series(np.random.random(len(dates))).rolling(30).mean() + 4 -fplt.plot(dates, prices, width=3) + +p = fplt.plot(dates, prices, width=3) + line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) ## fplt.remove_primitive(line) text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') ## fplt.remove_primitive(text) rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) + +arrow = fplt.add_arrow((dates[700], 4.3), direction="sell", interactive=False) + ## fplt.remove_primitive(rect) def save(): From c455c7a52b2f51907e28d8b631ba7f885c10f86f Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 15 Oct 2021 21:48:08 +0200 Subject: [PATCH 04/18] - Add trade functions to display open/close/profit on chart - Better API for drawing arrow --- finplot/__init__.py | 69 ++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 6f855e0..5167761 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -639,32 +639,23 @@ def addScaleHandle(self, *args, **kwargs): super().addScaleHandle(*args, **kwargs) class FinArrow(pg.ArrowItem): - def __init__(self, ax, direction, brushColor='', brushWidth=1, penColor='', penWidth=1, *args, **kwargs): - - if direction.lower() == "buy": - if brushColor == '': - brushColor = arrow_bull_color - if penColor == '': - penColor = arrow_bull_outline_color - kwargs['angle']=90 - kwargs['tipAngle']=30 - kwargs['baseAngle']=20 - elif direction.lower() == "sell": - if brushColor == '': - brushColor = arrow_bear_color - if penColor == '': - penColor = arrow_bear_outline_color - kwargs['angle']=-90 - kwargs['tipAngle']=30 - kwargs['baseAngle']=20 + def __init__(self, ax, angle, brushColor='', penColor='', brushWidth=1, penWidth=1, *args, **kwargs): + + kwargs['angle']=angle + kwargs['tipAngle']=30 + kwargs['baseAngle']=20 kwargs['headLen']=8 kwargs['headWidth']=8 kwargs['tailLen']=6 kwargs['tailWidth']=5 - kwargs['pen']={'color': penColor, 'width': penWidth} + if brushColor=='': + brushColor='#3030ff'; + if penColor=='': + penColor='#000'; + kwargs['pen']={'color': penColor, 'width': penWidth} brush = pg.mkBrush(brushColor) brush.width = brushWidth kwargs['brush'] = brush @@ -1609,6 +1600,40 @@ def labels(x, y=None, labels=None, color=None, ax=None, anchor=(0.5,1)): ax.vb.v_zoom_scale = 0.9 return item +def add_trade(posOpen, posClose, direction = "buy", profit = 0): + + # Open trade arrow + if direction.lower() == "buy": + brushColor = arrow_bull_color + penColor = arrow_bull_outline_color + add_arrow(posOpen, 90, brushColor, penColor) + + brushColor = arrow_bear_color + penColor = arrow_bear_outline_color + add_arrow(posClose, -90, brushColor, penColor) + + elif direction.lower() == "sell": + brushColor = arrow_bear_color + penColor = arrow_bear_outline_color + add_arrow(posOpen, -90, brushColor, penColor) + + # Close trade arrow + brushColor = arrow_bull_color + penColor = arrow_bull_outline_color + add_arrow(posClose, 90, brushColor, penColor) + + # Add dashed line + if profit > 0: + add_line(posOpen, posClose, "#30FF30", 2, style="-" ) + else: + add_line(posOpen, posClose, "#FF3030", 2, style=".") + + # Add label + mid = (posClose[0]-posOpen[0],posClose[1]-posOpen[1]) + textPos = (posOpen[0] + mid[0], posOpen[1] + mid[1]) + add_text(textPos,"+500") + + return def add_legend(text, ax=None): ax = _create_plot(ax=ax, maximize=False) @@ -1660,9 +1685,9 @@ def add_band(y0, y1, color=band_color, ax=None): ax.addItem(lr) return lr -def add_arrow(pos, direction="buy", arrow_color='', arrow_brush_width=1, arrow_outline_color='', arrow_outline_width=1, interactive=False, ax=None): +def add_arrow(pos, angle, arrow_color='', arrow_outline_color='', arrow_brush_width=1, arrow_outline_width=1, interactive=False, ax=None): ax = _create_plot(ax=ax, maximize=False) - arrow = FinArrow(ax, direction, arrow_color, arrow_brush_width, arrow_outline_color, arrow_outline_width) + arrow = FinArrow(ax, angle, arrow_color, arrow_outline_color, arrow_brush_width, arrow_outline_width) x = pos[0] if ax.vb.datasrc is not None: x = _pdtime2index(ax, pd.Series([pos[0]]))[0] @@ -2736,11 +2761,9 @@ def _makepen(color, style=None, width=1): dash[-1] += 2 return pg.mkPen(color=color, style=QtCore.Qt.CustomDashLine, dash=dash, width=width) - def _round(v): return floor(v+0.5) - try: qtver = '%d.%d' % (QtCore.QT_VERSION//256//256, QtCore.QT_VERSION//256%256) if qtver not in ('5.9', '5.13') and [int(i) for i in pg.__version__.split('.')] <= [0,11,0]: From 3b459635aa37c643ac7569e708d8acc9836277c6 Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 15 Oct 2021 21:57:49 +0200 Subject: [PATCH 05/18] Dash line for trades --- finplot/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 5167761..5043dff 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -53,7 +53,7 @@ arrow_bull_color = '#20FF20' arrow_bull_outline_color = '#222222' arrow_bear_color = '#f7a9a7' -arrow_bear_outline_color = '#f7a9a7' +arrow_bear_outline_color = '#222222' significant_decimals = 8 significant_eps = 1e-8 max_zoom_points = 20 # number of visible candles when maximum zoomed in @@ -1624,9 +1624,9 @@ def add_trade(posOpen, posClose, direction = "buy", profit = 0): # Add dashed line if profit > 0: - add_line(posOpen, posClose, "#30FF30", 2, style="-" ) + add_line(posOpen, posClose, "#30FF30", 2, style="--" ) else: - add_line(posOpen, posClose, "#FF3030", 2, style=".") + add_line(posOpen, posClose, "#FF3030", 2, style="..") # Add label mid = (posClose[0]-posOpen[0],posClose[1]-posOpen[1]) From 6efb253872d58dfc37153cf408f5bc807ffd35fd Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Fri, 22 Oct 2021 21:44:11 +0200 Subject: [PATCH 06/18] Add draw_order function --- finplot/__init__.py | 34 ++++++++++++++++---- finplot/examples/arrow.py | 55 +++++++++++++++++++++++++++++++++ finplot/examples/bfx.py | 4 +++ finplot/examples/complicated.py | 3 ++ finplot/examples/line.py | 11 ++++--- 5 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 finplot/examples/arrow.py diff --git a/finplot/__init__.py b/finplot/__init__.py index 5043dff..e952394 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -1600,27 +1600,49 @@ def labels(x, y=None, labels=None, color=None, ax=None, anchor=(0.5,1)): ax.vb.v_zoom_scale = 0.9 return item -def add_trade(posOpen, posClose, direction = "buy", profit = 0): - + +def add_order(datetime, price, direction = "buy", ax=None): + + # Open trade arrow + if direction.lower() == "buy": + brushColor = arrow_bull_color + penColor = arrow_bull_outline_color + angle = 90 + + elif direction.lower() == "sell": + brushColor = arrow_bear_color + penColor = arrow_bear_outline_color + angle = -90 + else: + brushColor = "" + penColor = "" + angle = 0 + + add_arrow((datetime,price), angle, brushColor, penColor, ax=ax) + + return + +def add_trade(posOpen, posClose, direction = "buy", profit = 0, ax=None): + # Open trade arrow if direction.lower() == "buy": brushColor = arrow_bull_color penColor = arrow_bull_outline_color - add_arrow(posOpen, 90, brushColor, penColor) + add_arrow(posOpen, 90, brushColor, penColor, ax=ax) brushColor = arrow_bear_color penColor = arrow_bear_outline_color - add_arrow(posClose, -90, brushColor, penColor) + add_arrow(posClose, -90, brushColor, penColor, ax=ax) elif direction.lower() == "sell": brushColor = arrow_bear_color penColor = arrow_bear_outline_color - add_arrow(posOpen, -90, brushColor, penColor) + add_arrow(posOpen, -90, brushColor, penColor, ax=ax) # Close trade arrow brushColor = arrow_bull_color penColor = arrow_bull_outline_color - add_arrow(posClose, 90, brushColor, penColor) + add_arrow(posClose, 90, brushColor, penColor, ax=ax) # Add dashed line if profit > 0: diff --git a/finplot/examples/arrow.py b/finplot/examples/arrow.py new file mode 100644 index 0000000..3af0b28 --- /dev/null +++ b/finplot/examples/arrow.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +Display an animated arrowhead following a curve. +This example uses the CurveArrow class, which is a combination +of ArrowItem and CurvePoint. + +To place a static arrow anywhere in a scene, use ArrowItem. +To attach other types of item to a curve, use CurvePoint. +""" + +import numpy as np +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph as pg + + +app = QtGui.QApplication([]) + +w = QtGui.QMainWindow() +cw = pg.GraphicsLayoutWidget() +w.show() +w.resize(400,600) +w.setCentralWidget(cw) +w.setWindowTitle('pyqtgraph example: Arrow') + +p = cw.addPlot(row=0, col=0) +p2 = cw.addPlot(row=1, col=0) + +## variety of arrow shapes +a1 = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}) +a2 = pg.ArrowItem(angle=-120, tipAngle=30, baseAngle=20, headLen=40, tailLen=40, tailWidth=8, pen=None, brush='y') +a3 = pg.ArrowItem(angle=-60, tipAngle=30, baseAngle=20, headLen=40, tailLen=None, brush=None) +a4 = pg.ArrowItem(angle=-20, tipAngle=30, baseAngle=-30, headLen=40, tailLen=None) +a2.setPos(10,0) +a3.setPos(20,0) +a4.setPos(30,0) +p.addItem(a1) +p.addItem(a2) +p.addItem(a3) +p.addItem(a4) +p.setRange(QtCore.QRectF(-20, -10, 60, 20)) + + +## Animated arrow following curve +c = p2.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000))) +a = pg.CurveArrow(c) +a.setStyle(headLen=40) +p2.addItem(a) +anim = a.makeAnimation(loop=-1) +anim.start() + +## Start Qt event loop unless running in interactive mode or using pyside. +if __name__ == '__main__': + import sys + if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): + QtGui.QApplication.instance().exec_() \ No newline at end of file diff --git a/finplot/examples/bfx.py b/finplot/examples/bfx.py index 8643e38..c480ea9 100644 --- a/finplot/examples/bfx.py +++ b/finplot/examples/bfx.py @@ -2,7 +2,11 @@ import math import pandas as pd + +import sys +sys.path.append('D:/perso/trading/anaconda3/finplot') import finplot as fplt + import requests import time diff --git a/finplot/examples/complicated.py b/finplot/examples/complicated.py index cc48a51..c7ace14 100644 --- a/finplot/examples/complicated.py +++ b/finplot/examples/complicated.py @@ -16,7 +16,10 @@ ''' +import sys +sys.path.append('D:/perso/trading/anaconda3/finplot') import finplot as fplt + from functools import lru_cache import json from math import nan diff --git a/finplot/examples/line.py b/finplot/examples/line.py index a4a0c04..427301e 100644 --- a/finplot/examples/line.py +++ b/finplot/examples/line.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -sys.path.append('D:/perso/trading/anaconda3/finplot') +sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import numpy as np import pandas as pd @@ -12,13 +12,14 @@ p = fplt.plot(dates, prices, width=3) -line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) +#line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) ## fplt.remove_primitive(line) -text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') +#text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') ## fplt.remove_primitive(text) -rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) +#rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) +#arrow = fplt.add_arrow((dates[700], 4.3), 180, interactive=False) -arrow = fplt.add_arrow((dates[700], 4.3), direction="sell", interactive=False) +fplt.add_trade((dates[300], 4.42),(dates[700], 4.56), "buy", 300) ## fplt.remove_primitive(rect) From 356ceaff4865a3428ae35d4470bede0aec41683b Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Wed, 10 Nov 2021 20:43:32 +0100 Subject: [PATCH 07/18] Ichimoku is displayed! Need to clean code --- finplot/__init__.py | 32 +++++++++++++++++------------- finplot/examples/pandas-df-plot.py | 1 + finplot/examples/snp500.py | 2 ++ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index e952394..34c05cb 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -21,6 +21,7 @@ import pandas as pd import pyqtgraph as pg from pyqtgraph import QtCore, QtGui +from pyqtgraph.dockarea.DockArea import DockArea @@ -1622,10 +1623,11 @@ def add_order(datetime, price, direction = "buy", ax=None): return -def add_trade(posOpen, posClose, direction = "buy", profit = 0, ax=None): +def add_trade(posOpen, posClose, isLong, profit = 0, ax=None): # Open trade arrow - if direction.lower() == "buy": + if isLong: + brushColor = arrow_bull_color penColor = arrow_bull_outline_color add_arrow(posOpen, 90, brushColor, penColor, ax=ax) @@ -1634,7 +1636,7 @@ def add_trade(posOpen, posClose, direction = "buy", profit = 0, ax=None): penColor = arrow_bear_outline_color add_arrow(posClose, -90, brushColor, penColor, ax=ax) - elif direction.lower() == "sell": + else: brushColor = arrow_bear_color penColor = arrow_bear_outline_color add_arrow(posOpen, -90, brushColor, penColor, ax=ax) @@ -1787,17 +1789,21 @@ def remove_primitive(primitive): ax.vb.removeItem(txt) -def set_time_inspector(inspector, ax=None, when='click'): +def set_time_inspector(inspector, ax=None, when='click', data=None): + '''Callback when clicked like so: inspector(x, y).''' ax = ax if ax else last_ax win = ax.vb.win + + if (type(win) is DockArea): + win = ax.ax_widget + if when == 'hover': - win.proxy_hover = pg.SignalProxy(win.scene().sigMouseMoved, rateLimit=15, slot=partial(_inspect_pos, ax, inspector)) + win.proxy_hover = pg.SignalProxy(win.scene().sigMouseMoved, rateLimit=15, slot=partial(_inspect_pos, ax, data, inspector)) elif when in ('dclick', 'double-click'): - win.proxy_dclick = pg.SignalProxy(win.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, inspector, True)) + win.proxy_dclick = pg.SignalProxy(win.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, data, inspector, True)) else: - win.proxy_click = pg.SignalProxy(win.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, inspector, False)) - + win.proxy_click = pg.SignalProxy(win.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, data, inspector, False)) def add_crosshair_info(infofunc, ax=None): '''Callback when crosshair updated like so: info(ax,x,y,xtext,ytext); the info() @@ -2479,15 +2485,13 @@ def _wheel_event_wrapper(self, orig_func, ev): ev = QtGui.QWheelEvent(ev.pos()+d, ev.globalPos()+d, ev.pixelDelta(), ev.angleDelta(), ev.angleDelta().y(), QtCore.Qt.Vertical, ev.buttons(), ev.modifiers()) orig_func(self, ev) - -def _inspect_clicked(ax, inspector, when_double_click, evs): +def _inspect_clicked(ax, data, inspector, when_double_click, evs): if evs[-1].accepted or when_double_click != evs[-1].double(): return pos = evs[-1].scenePos() - return _inspect_pos(ax, inspector, (pos,)) - + return _inspect_pos(ax, data, inspector, (pos,)) -def _inspect_pos(ax, inspector, poss): +def _inspect_pos(ax, data, inspector, poss): if not ax.vb.datasrc: return point = ax.vb.mapSceneToView(poss[-1]) @@ -2498,7 +2502,7 @@ def _inspect_pos(ax, inspector, poss): if clamp_grid: t = ax.vb.datasrc.x.iloc[-1 if t > 0 else 0] try: - inspector(t, point.y()) + inspector(t, point.y(), ax, data ) # or directly ax.vb.datasrc ? except OSError as e: pass except Exception as e: diff --git a/finplot/examples/pandas-df-plot.py b/finplot/examples/pandas-df-plot.py index 2cdf08a..f6ab7d5 100644 --- a/finplot/examples/pandas-df-plot.py +++ b/finplot/examples/pandas-df-plot.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +sys.path.append('../finplot') import finplot as fplt import pandas as pd diff --git a/finplot/examples/snp500.py b/finplot/examples/snp500.py index 8f2262b..967f0ae 100644 --- a/finplot/examples/snp500.py +++ b/finplot/examples/snp500.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import sys +sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import pandas as pd import requests From 6fa771205efa1782c5aa3fb1e18454418be85858 Mon Sep 17 00:00:00 2001 From: Skinok Todar <49732732+Skinok@users.noreply.github.com> Date: Thu, 18 Nov 2021 16:20:26 +0100 Subject: [PATCH 08/18] Candles are now on top of indicators --- finplot/__init__.py | 1 + finplot/examples/complicated.py | 5 ++--- finplot/examples/overlay-correlate.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 34c05cb..15f4dd6 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -1408,6 +1408,7 @@ def candlestick_ochl(datasrc, draw_body=True, draw_shadow=True, candle_width=0.6 _update_significants(ax, datasrc, force=True) item.update_data = partial(_update_data, None, None, item) item.update_gfx = partial(_update_gfx, item) + item.setZValue(40) # Skinok : candle should always be on top of any indicators ax.addItem(item) return item diff --git a/finplot/examples/complicated.py b/finplot/examples/complicated.py index c7ace14..9aaf1ef 100644 --- a/finplot/examples/complicated.py +++ b/finplot/examples/complicated.py @@ -15,9 +15,8 @@ more realistic. ''' - -import sys -sys.path.append('D:/perso/trading/anaconda3/finplot') +import sys, os +sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/../../../finplot') import finplot as fplt from functools import lru_cache diff --git a/finplot/examples/overlay-correlate.py b/finplot/examples/overlay-correlate.py index 7f2177f..0d18bc3 100644 --- a/finplot/examples/overlay-correlate.py +++ b/finplot/examples/overlay-correlate.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 from datetime import date, timedelta +import sys +sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import pandas as pd import scipy.optimize From 77f0787654a61479550b01c565372f6ebecc7a02 Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Wed, 12 Oct 2022 21:32:04 +0200 Subject: [PATCH 09/18] Add a function to move to a particular datetime in the chart (click on a trade) --- finplot/__init__.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/finplot/__init__.py b/finplot/__init__.py index 15f4dd6..ac2cfb3 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -16,6 +16,7 @@ from decimal import Decimal from functools import partial, partialmethod from math import ceil, floor, fmod +from wsgiref.headers import tspecials import numpy as np import os.path import pandas as pd @@ -2534,6 +2535,8 @@ def _get_color(ax, style, wanted_color): return colors[index%len(colors)] + + def _pdtime2epoch(t): if isinstance(t, pd.Series): if isinstance(t.iloc[0], pd.Timestamp): @@ -2548,7 +2551,53 @@ def _pdtime2epoch(t): return t.astype('int64') return t +# Skinok add +def _dateStr2x(ax, dateStr, any_end=False, require_time=False): + ts = pd.Series(pd.to_datetime(dateStr)) + if isinstance(ts.iloc[0], pd.Timestamp): + ts = ts.view('int64') + else: + h = np.nanmax(ts.values) + if h < 1e7: + if require_time: + assert False, 'not a time series' + return ts + if h < 1e10: # handle s epochs + ts = ts.astype('float64') * 1e9 + elif h < 1e13: # handle ms epochs + ts = ts.astype('float64') * 1e6 + elif h < 1e16: # handle us epochs + ts = ts.astype('float64') * 1e3 + + datasrc = _get_datasrc(ax) + xs = datasrc.x + + # try exact match before approximate match + exact = datasrc.index[xs.isin(ts)].to_list() + if len(exact) == len(ts): + return exact + + r = [] + for i,t in enumerate(ts): + xss = xs.loc[xs>t] + if len(xss) == 0: + t0 = xs.iloc[-1] + if any_end or t0 == t: + r.append(len(xs)-1) + continue + if i > 0: + continue + assert t <= t0, 'must plot this primitive in prior time-range' + i1 = xss.index[0] + i0 = i1-1 + if i0 < 0: + i0,i1 = 0,1 + t0,t1 = xs.loc[i0], xs.loc[i1] + dt = (t-t0) / (t1-t0) + r.append(lerp(dt, i0, i1)) + return r +# ts is "time series" here, not "timestamp" def _pdtime2index(ax, ts, any_end=False, require_time=False): if isinstance(ts.iloc[0], pd.Timestamp): ts = ts.view('int64') From 6764f6b51aed611416b78347fa23713f3a1769fd Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 14:33:00 +0200 Subject: [PATCH 10/18] Update Personnal path Need to be removed from repository) --- finplot/examples/bfx.py | 2 +- finplot/examples/dockable.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/finplot/examples/bfx.py b/finplot/examples/bfx.py index c480ea9..ad50940 100644 --- a/finplot/examples/bfx.py +++ b/finplot/examples/bfx.py @@ -4,7 +4,7 @@ import pandas as pd import sys -sys.path.append('D:/perso/trading/anaconda3/finplot') +sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import requests diff --git a/finplot/examples/dockable.py b/finplot/examples/dockable.py index 8017199..a257467 100644 --- a/finplot/examples/dockable.py +++ b/finplot/examples/dockable.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import sys +sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt from functools import lru_cache from PyQt5.QtWidgets import QApplication, QGridLayout, QMainWindow, QGraphicsView, QComboBox, QLabel From 2c63c8e5a91818ed4d04bc82af2147658bdc6c0f Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 14:44:40 +0200 Subject: [PATCH 11/18] Set Time Inspector : use data parameter --- finplot/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index cd55392..777b5ff 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -1969,11 +1969,11 @@ def set_time_inspector(inspector, ax=None, when='click', data=None): ax = ax if ax else last_ax master = ax.ax_widget if hasattr(ax, 'ax_widget') else ax.vb.win if when == 'hover': - master.proxy_hover = pg.SignalProxy(master.scene().sigMouseMoved, rateLimit=15, slot=partial(_inspect_pos, ax, inspector)) + master.proxy_hover = pg.SignalProxy(master.scene().sigMouseMoved, rateLimit=15, slot=partial(_inspect_pos, ax, data, inspector)) elif when in ('dclick', 'double-click'): - master.proxy_dclick = pg.SignalProxy(master.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, inspector, True)) + master.proxy_dclick = pg.SignalProxy(master.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, data, inspector, True)) else: - master.proxy_click = pg.SignalProxy(master.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, inspector, False)) + master.proxy_click = pg.SignalProxy(master.scene().sigMouseClicked, slot=partial(_inspect_clicked, ax, data, inspector, False)) def add_crosshair_info(infofunc, ax=None): '''Callback when crosshair updated like so: info(ax,x,y,xtext,ytext); the info() From 7779e63297b05d8227f0af2bb5e89dc883eeae88 Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 15:21:39 +0200 Subject: [PATCH 12/18] Start Migration to PyQt6 Clean up some finplot code. Start migration from PyQt5 to PyQt6. Does not compile yet : need QCoreApplication --- finplot/__init__.py | 17 ++++++----------- finplot/examples/bfx.py | 1 - finplot/examples/overlay-correlate.py | 1 - finplot/examples/pandas-df-plot.py | 1 - finplot/examples/snp500.py | 1 - 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 777b5ff..9df5c44 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -1388,17 +1388,6 @@ def generate_picture(self, boundingRect): p.setPen(pg.mkPen(poc_color)) p.drawLine(QtCore.QPointF(t, y), QtCore.QPointF(t+f*self.draw_poc, y)) - -class Arrow(): - def __init__(self, buy, color, anchor ): - self.color = color - self.text_items = {} - self.anchor = anchor - self.show = False - - - - class ScatterLabelItem(FinPlotItem): def __init__(self, ax, datasrc, color, anchor): self.color = color @@ -2773,7 +2762,13 @@ def _pdtime2epoch(t): return t.astype('int64') return t +# # Skinok add +# Use case : +# In case of backtesting, this function allow the user to click on a particular trade (in a trade history panel) +# and the chart will automatically move & center on the position of this trade +# This function returns the x position in the dataset, given the entry date +# def _dateStr2x(ax, dateStr, any_end=False, require_time=False): ts = pd.Series(pd.to_datetime(dateStr)) if isinstance(ts.iloc[0], pd.Timestamp): diff --git a/finplot/examples/bfx.py b/finplot/examples/bfx.py index 5287e78..90100f7 100644 --- a/finplot/examples/bfx.py +++ b/finplot/examples/bfx.py @@ -4,7 +4,6 @@ import pandas as pd import sys -sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import requests diff --git a/finplot/examples/overlay-correlate.py b/finplot/examples/overlay-correlate.py index 0d18bc3..3706591 100644 --- a/finplot/examples/overlay-correlate.py +++ b/finplot/examples/overlay-correlate.py @@ -2,7 +2,6 @@ from datetime import date, timedelta import sys -sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import pandas as pd import scipy.optimize diff --git a/finplot/examples/pandas-df-plot.py b/finplot/examples/pandas-df-plot.py index f6ab7d5..2cdf08a 100644 --- a/finplot/examples/pandas-df-plot.py +++ b/finplot/examples/pandas-df-plot.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -sys.path.append('../finplot') import finplot as fplt import pandas as pd diff --git a/finplot/examples/snp500.py b/finplot/examples/snp500.py index 22399bc..db9d44e 100644 --- a/finplot/examples/snp500.py +++ b/finplot/examples/snp500.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import sys -sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import pandas as pd import requests From ed7a2aecaedb4f26fe234125322014213b90f436 Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 15:22:52 +0200 Subject: [PATCH 13/18] Remove useless example --- finplot/examples/arrow.py | 55 --------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 finplot/examples/arrow.py diff --git a/finplot/examples/arrow.py b/finplot/examples/arrow.py deleted file mode 100644 index 3af0b28..0000000 --- a/finplot/examples/arrow.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Display an animated arrowhead following a curve. -This example uses the CurveArrow class, which is a combination -of ArrowItem and CurvePoint. - -To place a static arrow anywhere in a scene, use ArrowItem. -To attach other types of item to a curve, use CurvePoint. -""" - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = QtGui.QApplication([]) - -w = QtGui.QMainWindow() -cw = pg.GraphicsLayoutWidget() -w.show() -w.resize(400,600) -w.setCentralWidget(cw) -w.setWindowTitle('pyqtgraph example: Arrow') - -p = cw.addPlot(row=0, col=0) -p2 = cw.addPlot(row=1, col=0) - -## variety of arrow shapes -a1 = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}) -a2 = pg.ArrowItem(angle=-120, tipAngle=30, baseAngle=20, headLen=40, tailLen=40, tailWidth=8, pen=None, brush='y') -a3 = pg.ArrowItem(angle=-60, tipAngle=30, baseAngle=20, headLen=40, tailLen=None, brush=None) -a4 = pg.ArrowItem(angle=-20, tipAngle=30, baseAngle=-30, headLen=40, tailLen=None) -a2.setPos(10,0) -a3.setPos(20,0) -a4.setPos(30,0) -p.addItem(a1) -p.addItem(a2) -p.addItem(a3) -p.addItem(a4) -p.setRange(QtCore.QRectF(-20, -10, 60, 20)) - - -## Animated arrow following curve -c = p2.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000))) -a = pg.CurveArrow(c) -a.setStyle(headLen=40) -p2.addItem(a) -anim = a.makeAnimation(loop=-1) -anim.start() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file From 0d1033fdeba88e8cef9dba9d886a7aefa0d9d7c5 Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 15:25:12 +0200 Subject: [PATCH 14/18] Clean finplot examples --- finplot/examples/bfx.py | 1 - finplot/examples/complicated.py | 2 -- finplot/examples/dockable.py | 2 -- finplot/examples/line.py | 11 ++++------- finplot/examples/overlay-correlate.py | 1 - finplot/examples/snp500.py | 1 - 6 files changed, 4 insertions(+), 14 deletions(-) diff --git a/finplot/examples/bfx.py b/finplot/examples/bfx.py index 90100f7..bdcdd85 100644 --- a/finplot/examples/bfx.py +++ b/finplot/examples/bfx.py @@ -3,7 +3,6 @@ import math import pandas as pd -import sys import finplot as fplt import requests diff --git a/finplot/examples/complicated.py b/finplot/examples/complicated.py index 32aef36..48741b0 100644 --- a/finplot/examples/complicated.py +++ b/finplot/examples/complicated.py @@ -18,8 +18,6 @@ to be able to see real-time price action. ''' -import sys, os -sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/../../../finplot') import finplot as fplt from functools import lru_cache diff --git a/finplot/examples/dockable.py b/finplot/examples/dockable.py index d73e082..3c8c8f4 100644 --- a/finplot/examples/dockable.py +++ b/finplot/examples/dockable.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import sys -sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt from functools import lru_cache from PyQt6.QtWidgets import QApplication, QGridLayout, QMainWindow, QGraphicsView, QComboBox, QLabel diff --git a/finplot/examples/line.py b/finplot/examples/line.py index 427301e..d8f6324 100644 --- a/finplot/examples/line.py +++ b/finplot/examples/line.py @@ -11,15 +11,12 @@ prices = pd.Series(np.random.random(len(dates))).rolling(30).mean() + 4 p = fplt.plot(dates, prices, width=3) - -#line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) +line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) ## fplt.remove_primitive(line) -#text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') +text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') ## fplt.remove_primitive(text) -#rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) -#arrow = fplt.add_arrow((dates[700], 4.3), 180, interactive=False) - -fplt.add_trade((dates[300], 4.42),(dates[700], 4.56), "buy", 300) +rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) +arrow = fplt.add_arrow((dates[700], 4.3), 180, interactive=False) ## fplt.remove_primitive(rect) diff --git a/finplot/examples/overlay-correlate.py b/finplot/examples/overlay-correlate.py index 3706591..7f2177f 100644 --- a/finplot/examples/overlay-correlate.py +++ b/finplot/examples/overlay-correlate.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from datetime import date, timedelta -import sys import finplot as fplt import pandas as pd import scipy.optimize diff --git a/finplot/examples/snp500.py b/finplot/examples/snp500.py index db9d44e..dd63ce3 100644 --- a/finplot/examples/snp500.py +++ b/finplot/examples/snp500.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import sys import finplot as fplt import pandas as pd import requests From 0dd6c10efccc27bd083e5da2a036da2333beb1af Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 14 May 2023 15:27:11 +0200 Subject: [PATCH 15/18] Still cleaning examples --- finplot/examples/bfx.py | 2 -- finplot/examples/complicated.py | 2 -- finplot/examples/line.py | 7 +------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/finplot/examples/bfx.py b/finplot/examples/bfx.py index bdcdd85..b71aba0 100644 --- a/finplot/examples/bfx.py +++ b/finplot/examples/bfx.py @@ -2,9 +2,7 @@ import math import pandas as pd - import finplot as fplt - import requests import time diff --git a/finplot/examples/complicated.py b/finplot/examples/complicated.py index 48741b0..7a09290 100644 --- a/finplot/examples/complicated.py +++ b/finplot/examples/complicated.py @@ -17,9 +17,7 @@ You'll need to "pip install websocket-client" before running this to be able to see real-time price action. ''' - import finplot as fplt - from functools import lru_cache import json from math import nan diff --git a/finplot/examples/line.py b/finplot/examples/line.py index d8f6324..5777a3a 100644 --- a/finplot/examples/line.py +++ b/finplot/examples/line.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import sys -sys.path.append('C:/perso/trading/anaconda3/finplot') import finplot as fplt import numpy as np import pandas as pd @@ -9,15 +7,12 @@ dates = pd.date_range('01:00', '01:00:01.200', freq='1ms') prices = pd.Series(np.random.random(len(dates))).rolling(30).mean() + 4 - -p = fplt.plot(dates, prices, width=3) +fplt.plot(dates, prices, width=3) line = fplt.add_line((dates[100], 4.4), (dates[1100], 4.6), color='#9900ff', interactive=True) ## fplt.remove_primitive(line) text = fplt.add_text((dates[500], 4.6), "I'm here alright!", color='#bb7700') ## fplt.remove_primitive(text) rect = fplt.add_rect((dates[700], 4.5), (dates[850], 4.4), color='#8c8', interactive=True) -arrow = fplt.add_arrow((dates[700], 4.3), 180, interactive=False) - ## fplt.remove_primitive(rect) def save(): From d2408dbc72586eea68e7376a0b6b09d5e2379e1c Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Fri, 26 May 2023 12:06:38 +0200 Subject: [PATCH 16/18] Naming changes to order / trade function --- finplot/__init__.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 9df5c44..4c532e1 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -1765,32 +1765,28 @@ def live(plots=1): return Live() return [Live() for _ in range(plots)] -def add_order(datetime, price, direction = "buy", ax=None): + +def add_order(datetime, price, isLong, ax=None): # Open trade arrow - if direction.lower() == "buy": + if isLong: brushColor = arrow_bull_color penColor = arrow_bull_outline_color angle = 90 - elif direction.lower() == "sell": + else: brushColor = arrow_bear_color penColor = arrow_bear_outline_color angle = -90 - else: - brushColor = "" - penColor = "" - angle = 0 add_arrow((datetime,price), angle, brushColor, penColor, ax=ax) return -def add_trade(posOpen, posClose, isLong, profit = 0, ax=None): +def add_trade(posOpen, posClose, isLong, isWinningTrade, ax=None): # Open trade arrow if isLong: - brushColor = arrow_bull_color penColor = arrow_bull_outline_color add_arrow(posOpen, 90, brushColor, penColor, ax=ax) @@ -1810,7 +1806,7 @@ def add_trade(posOpen, posClose, isLong, profit = 0, ax=None): add_arrow(posClose, 90, brushColor, penColor, ax=ax) # Add dashed line - if profit > 0: + if isWinningTrade: add_line(posOpen, posClose, "#30FF30", 2, style="--" ) else: add_line(posOpen, posClose, "#FF3030", 2, style="..") From bbcdd62573784669e54e674f4ccdb197fe10de02 Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Fri, 26 May 2023 12:19:24 +0200 Subject: [PATCH 17/18] Add whitespaces --- finplot/examples/complicated.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/finplot/examples/complicated.py b/finplot/examples/complicated.py index 7a09290..79de3b2 100644 --- a/finplot/examples/complicated.py +++ b/finplot/examples/complicated.py @@ -17,6 +17,8 @@ You'll need to "pip install websocket-client" before running this to be able to see real-time price action. ''' + + import finplot as fplt from functools import lru_cache import json From 98575e273c43ceaefbc29c0fbfea879c55c07cfc Mon Sep 17 00:00:00 2001 From: Frederic Lambert Date: Sun, 28 May 2023 18:53:28 +0200 Subject: [PATCH 18/18] Fix crash when using multiple Windows --- finplot/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/finplot/__init__.py b/finplot/__init__.py index 4c532e1..a32a499 100644 --- a/finplot/__init__.py +++ b/finplot/__init__.py @@ -2668,7 +2668,13 @@ def _mouse_clicked(vb, ev): def _mouse_moved(master, vb, evs): if hasattr(master, 'closing') and master.closing: return - md = master_data[master].get(vb) or master_data[master]['default'] + + md = master_data[master].get(vb) + if md is None: + if 'default' in master_data[master]: + md = master_data[master]['default'] + if md is None: + return if not evs: evs = md['last_mouse_evs'] if not evs: