[Add] candle chart module
This commit is contained in:
parent
31f017e8a4
commit
35d9cacf33
23
examples/candle_chart/run.py
Normal file
23
examples/candle_chart/run.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from vnpy.trader.ui import QtWidgets
|
||||||
|
from vnpy.chart import ChartWidget
|
||||||
|
from vnpy.trader.database import database_manager
|
||||||
|
from vnpy.trader.constant import Exchange, Interval
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
|
bars = database_manager.load_bar_data(
|
||||||
|
"IF88",
|
||||||
|
Exchange.CFFEX,
|
||||||
|
interval=Interval.MINUTE,
|
||||||
|
start=datetime(2019, 7, 1),
|
||||||
|
end=datetime(2019, 7, 17)
|
||||||
|
)
|
||||||
|
|
||||||
|
widget = ChartWidget()
|
||||||
|
widget.update_history(bars)
|
||||||
|
widget.show()
|
||||||
|
|
||||||
|
app.exec_()
|
@ -8,7 +8,7 @@ from vnpy.trader.ui import MainWindow, create_qapp
|
|||||||
# from vnpy.gateway.bitmex import BitmexGateway
|
# from vnpy.gateway.bitmex import BitmexGateway
|
||||||
# from vnpy.gateway.futu import FutuGateway
|
# from vnpy.gateway.futu import FutuGateway
|
||||||
# from vnpy.gateway.ib import IbGateway
|
# from vnpy.gateway.ib import IbGateway
|
||||||
# from vnpy.gateway.ctp import CtpGateway
|
from vnpy.gateway.ctp import CtpGateway
|
||||||
# from vnpy.gateway.ctptest import CtptestGateway
|
# from vnpy.gateway.ctptest import CtptestGateway
|
||||||
# from vnpy.gateway.mini import MiniGateway
|
# from vnpy.gateway.mini import MiniGateway
|
||||||
# from vnpy.gateway.femas import FemasGateway
|
# from vnpy.gateway.femas import FemasGateway
|
||||||
@ -44,7 +44,7 @@ def main():
|
|||||||
main_engine = MainEngine(event_engine)
|
main_engine = MainEngine(event_engine)
|
||||||
|
|
||||||
# main_engine.add_gateway(BinanceGateway)
|
# main_engine.add_gateway(BinanceGateway)
|
||||||
# main_engine.add_gateway(CtpGateway)
|
main_engine.add_gateway(CtpGateway)
|
||||||
# main_engine.add_gateway(CtptestGateway)
|
# main_engine.add_gateway(CtptestGateway)
|
||||||
# main_engine.add_gateway(MiniGateway)
|
# main_engine.add_gateway(MiniGateway)
|
||||||
# main_engine.add_gateway(FemasGateway)
|
# main_engine.add_gateway(FemasGateway)
|
||||||
|
1
vnpy/chart/__init__.py
Normal file
1
vnpy/chart/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .widget import ChartWidget
|
44
vnpy/chart/axis.py
Normal file
44
vnpy/chart/axis.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
from vnpy.trader.ui import QtGui
|
||||||
|
|
||||||
|
from .manager import BarManager
|
||||||
|
|
||||||
|
|
||||||
|
AXIS_WIDTH = 0.8
|
||||||
|
AXIS_FONT = QtGui.QFont("Arial", 10, QtGui.QFont.Bold)
|
||||||
|
|
||||||
|
|
||||||
|
class DatetimeAxis(pg.AxisItem):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, manager: BarManager, *args, **kwargs):
|
||||||
|
""""""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._manager: BarManager = manager
|
||||||
|
|
||||||
|
self.setPen(width=AXIS_WIDTH)
|
||||||
|
self.setStyle(tickFont=AXIS_FONT, autoExpandTextSpace=True)
|
||||||
|
|
||||||
|
def tickStrings(self, values: List[int], scale: float, spacing: int):
|
||||||
|
"""
|
||||||
|
Convert original index to datetime string.
|
||||||
|
"""
|
||||||
|
strings = []
|
||||||
|
|
||||||
|
for ix in values:
|
||||||
|
dt = self._manager.get_datetime(ix)
|
||||||
|
|
||||||
|
if not dt:
|
||||||
|
s = ""
|
||||||
|
elif dt.hour:
|
||||||
|
s = dt.strftime("%Y-%m-%d\n%H:%M:%S")
|
||||||
|
else:
|
||||||
|
s = dt.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
strings.append(s)
|
||||||
|
|
||||||
|
return strings
|
9
vnpy/chart/base.py
Normal file
9
vnpy/chart/base.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
WHITE_COLOR = (255, 255, 255)
|
||||||
|
BLACK_COLOR = (0, 0, 0)
|
||||||
|
GREY_COLOR = (100, 100, 100)
|
||||||
|
|
||||||
|
UP_COLOR = (85, 234, 204)
|
||||||
|
DOWN_COLOR = (218, 75, 61)
|
||||||
|
|
||||||
|
PEN_WIDTH = 1
|
||||||
|
BAR_WIDTH = 0.4
|
9
vnpy/chart/cursor.py
Normal file
9
vnpy/chart/cursor.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from vnpy.trader.ui import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
class ChartCursor(QtCore.QObject):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
""""""
|
||||||
|
pass
|
232
vnpy/chart/item.py
Normal file
232
vnpy/chart/item.py
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
from vnpy.trader.ui import QtCore, QtGui, QtWidgets
|
||||||
|
from vnpy.trader.object import BarData
|
||||||
|
|
||||||
|
from .base import UP_COLOR, DOWN_COLOR, PEN_WIDTH, BAR_WIDTH
|
||||||
|
from .manager import BarManager
|
||||||
|
|
||||||
|
|
||||||
|
class ChartItem(pg.GraphicsObject):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, manager: BarManager):
|
||||||
|
""""""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._manager: BarManager = manager
|
||||||
|
|
||||||
|
self._bar_picutures: Dict[int, QtGui.QPicture] = {}
|
||||||
|
self._item_picuture: QtGui.QPicture = None
|
||||||
|
|
||||||
|
self._up_pen: QtGui.QPen = pg.mkPen(
|
||||||
|
color=UP_COLOR, width=PEN_WIDTH
|
||||||
|
)
|
||||||
|
self._up_brush: QtGui.QBrush = pg.mkBrush(color=UP_COLOR)
|
||||||
|
|
||||||
|
self._down_pen: QtGui.QPen = pg.mkPen(
|
||||||
|
color=DOWN_COLOR, width=PEN_WIDTH
|
||||||
|
)
|
||||||
|
self._down_brush: QtGui.QBrush = pg.mkBrush(color=DOWN_COLOR)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
|
||||||
|
"""
|
||||||
|
Draw picture for specific bar.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def boundingRect(self):
|
||||||
|
"""
|
||||||
|
Get bounding rectangles for item.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_history(self, history: List[BarData]) -> BarData:
|
||||||
|
"""
|
||||||
|
Update a list of bar data.
|
||||||
|
"""
|
||||||
|
self._bar_picutures.clear()
|
||||||
|
|
||||||
|
bars = self._manager.get_all_bars()
|
||||||
|
for ix, bar in enumerate(bars):
|
||||||
|
bar_picture = self._draw_bar_picture(ix, bar)
|
||||||
|
self._bar_picutures[ix] = bar_picture
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update_bar(self, bar: BarData) -> BarData:
|
||||||
|
"""
|
||||||
|
Update single bar data.
|
||||||
|
"""
|
||||||
|
ix = self._manager.get_index(bar.datetime)
|
||||||
|
|
||||||
|
bar_picture = self._draw_bar_picture(ix, bar)
|
||||||
|
self._bar_picutures[ix] = bar_picture
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
"""
|
||||||
|
Refresh the item.
|
||||||
|
"""
|
||||||
|
if self.scene():
|
||||||
|
self.scene().update()
|
||||||
|
|
||||||
|
def paint(
|
||||||
|
self,
|
||||||
|
painter: QtGui.QPainter,
|
||||||
|
opt: QtWidgets.QStyleOptionGraphicsItem,
|
||||||
|
w: QtWidgets.QWidget
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Reimplement the paint method of parent class.
|
||||||
|
|
||||||
|
This function is called by external QGraphicsView.
|
||||||
|
"""
|
||||||
|
rect = opt.exposedRect
|
||||||
|
min_ix = int(rect.left())
|
||||||
|
max_ix = int(rect.right())
|
||||||
|
max_ix = min(max_ix, len(self._bar_picutures))
|
||||||
|
|
||||||
|
self._draw__item_picuture(min_ix, max_ix)
|
||||||
|
self._item_picuture.play(painter)
|
||||||
|
|
||||||
|
def _draw__item_picuture(self, min_ix: int, max_ix: int) -> None:
|
||||||
|
"""
|
||||||
|
Draw the picture of item in specific range.
|
||||||
|
"""
|
||||||
|
self._item_picuture = QtGui.QPicture()
|
||||||
|
painter = QtGui.QPainter(self._item_picuture)
|
||||||
|
|
||||||
|
for n in range(min_ix, max_ix):
|
||||||
|
bar_picture = self._bar_picutures[n]
|
||||||
|
bar_picture.play(painter)
|
||||||
|
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
def clear_all(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear all data in the item.
|
||||||
|
"""
|
||||||
|
self._item_picuture = None
|
||||||
|
self._bar_picutures.clear()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
class CandleItem(ChartItem):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, manager: BarManager):
|
||||||
|
""""""
|
||||||
|
super().__init__(manager)
|
||||||
|
|
||||||
|
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
|
||||||
|
""""""
|
||||||
|
# Create objects
|
||||||
|
candle_picture = QtGui.QPicture()
|
||||||
|
painter = QtGui.QPainter(candle_picture)
|
||||||
|
|
||||||
|
# Set painter color
|
||||||
|
if bar.close_price >= bar.open_price:
|
||||||
|
painter.setPen(self._up_pen)
|
||||||
|
painter.setBrush(self._up_brush)
|
||||||
|
else:
|
||||||
|
painter.setPen(self._down_pen)
|
||||||
|
painter.setBrush(self._down_brush)
|
||||||
|
|
||||||
|
# Draw candle body
|
||||||
|
if bar.open_price == bar.close_price:
|
||||||
|
painter.drawLine(
|
||||||
|
QtCore.QPointF(ix - BAR_WIDTH, bar.open_price),
|
||||||
|
QtCore.QPointF(ix + BAR_WIDTH, bar.open_price),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
rect = QtCore.QRectF(
|
||||||
|
ix - BAR_WIDTH,
|
||||||
|
bar.open_price,
|
||||||
|
BAR_WIDTH * 2,
|
||||||
|
bar.close_price - bar.open_price
|
||||||
|
)
|
||||||
|
painter.drawRect(rect)
|
||||||
|
|
||||||
|
# Draw candle shadow
|
||||||
|
body_bottom = min(bar.open_price, bar.close_price)
|
||||||
|
body_top = max(bar.open_price, bar.close_price)
|
||||||
|
|
||||||
|
if bar.low_price < body_bottom:
|
||||||
|
painter.drawLine(
|
||||||
|
QtCore.QPointF(ix, bar.low_price),
|
||||||
|
QtCore.QPointF(ix, body_bottom),
|
||||||
|
)
|
||||||
|
|
||||||
|
if bar.high_price > body_top:
|
||||||
|
painter.drawLine(
|
||||||
|
QtCore.QPointF(ix, bar.high_price),
|
||||||
|
QtCore.QPointF(ix, body_top),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finish
|
||||||
|
painter.end()
|
||||||
|
return candle_picture
|
||||||
|
|
||||||
|
def boundingRect(self) -> QtCore.QRectF:
|
||||||
|
""""""
|
||||||
|
min_price, max_price = self._manager.get_price_range()
|
||||||
|
rect = QtCore.QRectF(
|
||||||
|
0,
|
||||||
|
max_price,
|
||||||
|
len(self._bar_picutures),
|
||||||
|
max_price - min_price
|
||||||
|
)
|
||||||
|
return rect
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeItem(ChartItem):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, manager: BarManager):
|
||||||
|
""""""
|
||||||
|
super().__init__(manager)
|
||||||
|
|
||||||
|
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
|
||||||
|
""""""
|
||||||
|
# Create objects
|
||||||
|
volume_picture = QtGui.QPicture()
|
||||||
|
painter = QtGui.QPainter(volume_picture)
|
||||||
|
|
||||||
|
# Set painter color
|
||||||
|
if bar.close_price >= bar.open_price:
|
||||||
|
painter.setPen(self._up_pen)
|
||||||
|
painter.setBrush(self._up_brush)
|
||||||
|
else:
|
||||||
|
painter.setPen(self._down_pen)
|
||||||
|
painter.setBrush(self._down_brush)
|
||||||
|
|
||||||
|
# Draw volume body
|
||||||
|
rect = QtCore.QRectF(
|
||||||
|
ix - BAR_WIDTH,
|
||||||
|
0,
|
||||||
|
BAR_WIDTH * 2,
|
||||||
|
bar.volume
|
||||||
|
)
|
||||||
|
painter.drawRect(rect)
|
||||||
|
|
||||||
|
# Finish
|
||||||
|
painter.end()
|
||||||
|
return volume_picture
|
||||||
|
|
||||||
|
def boundingRect(self) -> QtCore.QRectF:
|
||||||
|
""""""
|
||||||
|
min_volume, max_volume = self._manager.get_volume_range()
|
||||||
|
rect = QtCore.QRectF(
|
||||||
|
0,
|
||||||
|
min_volume,
|
||||||
|
len(self._bar_picutures),
|
||||||
|
max_volume - min_volume
|
||||||
|
)
|
||||||
|
return rect
|
151
vnpy/chart/manager.py
Normal file
151
vnpy/chart/manager.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
from typing import Dict, List, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from vnpy.trader.object import BarData
|
||||||
|
|
||||||
|
|
||||||
|
class BarManager:
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
""""""
|
||||||
|
self._bars: Dict[datetime, BarData] = {}
|
||||||
|
self._datetime_index_map: Dict[datetime, int] = {}
|
||||||
|
self._index_datetime_map: Dict[int, datetime] = {}
|
||||||
|
|
||||||
|
def update_history(self, history: List[BarData]) -> None:
|
||||||
|
"""
|
||||||
|
Update a list of bar data.
|
||||||
|
"""
|
||||||
|
# Put all new bars into dict
|
||||||
|
for bar in history:
|
||||||
|
self._bars[bar.datetime] = bar
|
||||||
|
|
||||||
|
# Sort bars dict according to bar.datetime
|
||||||
|
self._bars = dict(sorted(self._bars.items(), key=lambda tp: tp[0]))
|
||||||
|
|
||||||
|
# Update map relationiship
|
||||||
|
ix_list = range(len(self._bars))
|
||||||
|
dt_list = self._bars.keys()
|
||||||
|
|
||||||
|
self._datetime_index_map = dict(zip(dt_list, ix_list))
|
||||||
|
self._index_datetime_map = dict(zip(ix_list, dt_list))
|
||||||
|
|
||||||
|
# Clear data range cache
|
||||||
|
self._clear_cache()
|
||||||
|
|
||||||
|
def update_bar(self, bar: BarData) -> None:
|
||||||
|
"""
|
||||||
|
Update one single bar data.
|
||||||
|
"""
|
||||||
|
dt = bar.datetime
|
||||||
|
|
||||||
|
if dt not in self._datetime_index_map:
|
||||||
|
ix = len(self._bars)
|
||||||
|
self._datetime_index_map[dt] = ix
|
||||||
|
self._index_datetime_map[ix] = dt
|
||||||
|
|
||||||
|
self.datetime_bar_map[dt] = bar
|
||||||
|
|
||||||
|
self._clear_cache()
|
||||||
|
|
||||||
|
def get_count(self) -> int:
|
||||||
|
"""
|
||||||
|
Get total number of bars.
|
||||||
|
"""
|
||||||
|
return len(self._bars)
|
||||||
|
|
||||||
|
def get_index(self, dt: datetime) -> int:
|
||||||
|
"""
|
||||||
|
Get index with datetime.
|
||||||
|
"""
|
||||||
|
return self._datetime_index_map.get(dt, None)
|
||||||
|
|
||||||
|
def get_datetime(self, ix: int) -> datetime:
|
||||||
|
"""
|
||||||
|
Get datetime with index.
|
||||||
|
"""
|
||||||
|
return self._index_datetime_map.get(ix, None)
|
||||||
|
|
||||||
|
def get_bar(self, ix: int) -> BarData:
|
||||||
|
"""
|
||||||
|
Get bar data with index.
|
||||||
|
"""
|
||||||
|
dt = self._index_datetime_map.get(ix, None)
|
||||||
|
if not dt:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._bars[dt]
|
||||||
|
|
||||||
|
def get_all_bars(self) -> List[BarData]:
|
||||||
|
"""
|
||||||
|
Get all bar data.
|
||||||
|
"""
|
||||||
|
return list(self._bars.values())
|
||||||
|
|
||||||
|
@lru_cache(maxsize=99999)
|
||||||
|
def get_price_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
|
||||||
|
"""
|
||||||
|
Get price range to show within given index range.
|
||||||
|
"""
|
||||||
|
if not self._bars:
|
||||||
|
return 0, 1
|
||||||
|
|
||||||
|
if not min_ix:
|
||||||
|
min_ix = 0
|
||||||
|
max_ix = len(self._bars) - 1
|
||||||
|
else:
|
||||||
|
max_ix = min(max_ix, self.get_count())
|
||||||
|
|
||||||
|
bar_list = list(self._bars.values())[min_ix:max_ix + 1]
|
||||||
|
first_bar = bar_list[0]
|
||||||
|
max_price = first_bar.high_price
|
||||||
|
min_price = first_bar.low_price
|
||||||
|
|
||||||
|
for bar in bar_list[1:]:
|
||||||
|
max_price = max(max_price, bar.high_price)
|
||||||
|
min_price = min(min_price, bar.low_price)
|
||||||
|
|
||||||
|
return min_price, max_price
|
||||||
|
|
||||||
|
@lru_cache(maxsize=99999)
|
||||||
|
def get_volume_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
|
||||||
|
"""
|
||||||
|
Get volume range to show within given index range.
|
||||||
|
"""
|
||||||
|
if not self._bars:
|
||||||
|
return 0, 1
|
||||||
|
|
||||||
|
if not min_ix:
|
||||||
|
min_ix = 0
|
||||||
|
max_ix = len(self._bars) - 1
|
||||||
|
|
||||||
|
max_ix = min(max_ix, self.get_count())
|
||||||
|
bar_list = list(self._bars.values())[min_ix:max_ix + 1]
|
||||||
|
|
||||||
|
first_bar = bar_list[0]
|
||||||
|
max_volume = first_bar.volume
|
||||||
|
min_volume = 0
|
||||||
|
|
||||||
|
for bar in bar_list[1:]:
|
||||||
|
max_volume = max(max_volume, bar.volume)
|
||||||
|
|
||||||
|
return min_volume, max_volume
|
||||||
|
|
||||||
|
def _clear_cache(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear lru_cache range data.
|
||||||
|
"""
|
||||||
|
self.get_price_range.cache_clear()
|
||||||
|
self.get_volume_range.cache_clear()
|
||||||
|
|
||||||
|
def clear_all(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear all data in manager.
|
||||||
|
"""
|
||||||
|
self._bars.clear()
|
||||||
|
self._datetime_index_map.clear()
|
||||||
|
self._index_datetime_map.clear()
|
||||||
|
|
||||||
|
self._clear_cache()
|
179
vnpy/chart/widget.py
Normal file
179
vnpy/chart/widget.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
from vnpy.trader.ui import QtGui, QtWidgets
|
||||||
|
from vnpy.trader.object import BarData
|
||||||
|
|
||||||
|
from .manager import BarManager
|
||||||
|
from .base import GREY_COLOR
|
||||||
|
from .axis import DatetimeAxis, AXIS_FONT
|
||||||
|
from .item import CandleItem, VolumeItem, ChartItem
|
||||||
|
|
||||||
|
|
||||||
|
class ChartWidget(pg.PlotWidget):
|
||||||
|
""""""
|
||||||
|
MIN_BAR_COUNT = 100
|
||||||
|
|
||||||
|
def __init__(self, parent: QtWidgets.QWidget = None):
|
||||||
|
""""""
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._manager: BarManager = BarManager()
|
||||||
|
|
||||||
|
self._plots: List[ChartItem] = []
|
||||||
|
self._items: List[pg.GraphicsObject] = []
|
||||||
|
|
||||||
|
self._max_ix: int = 0
|
||||||
|
self._bar_count: int = 0
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self) -> None:
|
||||||
|
""""""
|
||||||
|
self._layout = pg.GraphicsLayout()
|
||||||
|
self._layout.setContentsMargins(10, 10, 10, 10)
|
||||||
|
self._layout.setSpacing(0)
|
||||||
|
self._layout.setBorder(color=GREY_COLOR, width=0.8)
|
||||||
|
self._layout.setZValue(0)
|
||||||
|
|
||||||
|
self.setCentralItem(self._layout)
|
||||||
|
|
||||||
|
self._x_axis = DatetimeAxis(self._manager, orientation='bottom')
|
||||||
|
|
||||||
|
self.init_candle()
|
||||||
|
self.init_volume()
|
||||||
|
self._volume_plot.setXLink(self._candle_plot)
|
||||||
|
|
||||||
|
def new_plot(self) -> None:
|
||||||
|
""""""
|
||||||
|
plot = pg.PlotItem(axisItems={'bottom': self._x_axis})
|
||||||
|
plot.setMenuEnabled(False)
|
||||||
|
plot.setClipToView(True)
|
||||||
|
plot.hideAxis('left')
|
||||||
|
plot.showAxis('right')
|
||||||
|
plot.setDownsampling(mode='peak')
|
||||||
|
plot.setRange(xRange=(0, 1), yRange=(0, 1))
|
||||||
|
plot.hideButtons()
|
||||||
|
|
||||||
|
view = plot.getViewBox()
|
||||||
|
view.sigXRangeChanged.connect(self._change_plot_y_range)
|
||||||
|
view.setMouseEnabled(x=True, y=False)
|
||||||
|
|
||||||
|
right_axis = plot.getAxis('right')
|
||||||
|
right_axis.setWidth(60)
|
||||||
|
right_axis.setStyle(tickFont=AXIS_FONT)
|
||||||
|
|
||||||
|
return plot
|
||||||
|
|
||||||
|
def init_candle(self) -> None:
|
||||||
|
"""
|
||||||
|
Initialize candle plot.
|
||||||
|
"""
|
||||||
|
self._candle_item = CandleItem(self._manager)
|
||||||
|
self._items.append(self._candle_item)
|
||||||
|
|
||||||
|
self._candle_plot = self.new_plot()
|
||||||
|
self._candle_plot.addItem(self._candle_item)
|
||||||
|
self._candle_plot.setMinimumHeight(80)
|
||||||
|
self._candle_plot.hideAxis('bottom')
|
||||||
|
self._plots.append(self._candle_plot)
|
||||||
|
|
||||||
|
self._layout.nextRow()
|
||||||
|
self._layout.addItem(self._candle_plot)
|
||||||
|
|
||||||
|
def init_volume(self) -> None:
|
||||||
|
"""
|
||||||
|
Initialize bar plot.
|
||||||
|
"""
|
||||||
|
self._volume_item = VolumeItem(self._manager)
|
||||||
|
self._items.append(self._volume_item)
|
||||||
|
|
||||||
|
self._volume_plot = self.new_plot()
|
||||||
|
self._volume_plot.addItem(self._volume_item)
|
||||||
|
self._volume_plot.setMinimumHeight(80)
|
||||||
|
self._plots.append(self._volume_plot)
|
||||||
|
|
||||||
|
self._layout.nextRow()
|
||||||
|
self._layout.addItem(self._volume_plot)
|
||||||
|
|
||||||
|
def clear_all(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear all data.
|
||||||
|
"""
|
||||||
|
self._manager.clear_all()
|
||||||
|
|
||||||
|
for item in self._items:
|
||||||
|
item.clear_all()
|
||||||
|
|
||||||
|
def update_history(self, history: List[BarData]) -> None:
|
||||||
|
"""
|
||||||
|
Update a list of bar data.
|
||||||
|
"""
|
||||||
|
self._manager.update_history(history)
|
||||||
|
|
||||||
|
for item in self._items:
|
||||||
|
item.update_history(history)
|
||||||
|
|
||||||
|
self._update_plot_range()
|
||||||
|
|
||||||
|
def update_bar(self, bar: BarData) -> None:
|
||||||
|
"""
|
||||||
|
Update single bar data.
|
||||||
|
"""
|
||||||
|
self._manager.update_bar(bar)
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
item.update_bar(bar)
|
||||||
|
|
||||||
|
self._update_plot_range()
|
||||||
|
|
||||||
|
def _update_plot_range(self) -> None:
|
||||||
|
"""
|
||||||
|
Update the range of plots.
|
||||||
|
"""
|
||||||
|
max_ix = self._max_ix
|
||||||
|
min_ix = self._max_ix - self._bar_count
|
||||||
|
|
||||||
|
# Update limit and range for x-axis
|
||||||
|
for plot in self._plots:
|
||||||
|
plot.setLimits(
|
||||||
|
xMin=-self.MIN_BAR_COUNT,
|
||||||
|
xMax=self._manager.get_count()
|
||||||
|
)
|
||||||
|
plot.setRange(
|
||||||
|
xRange=(min_ix, max_ix),
|
||||||
|
padding=0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update limit for y-axis
|
||||||
|
min_price, max_price = self._manager.get_price_range()
|
||||||
|
self._candle_plot.setLimits(yMin=min_price, yMax=max_price)
|
||||||
|
|
||||||
|
min_volume, max_volume = self._manager.get_volume_range()
|
||||||
|
self._volume_plot.setLimits(yMin=min_volume, yMax=max_volume)
|
||||||
|
|
||||||
|
def _change_plot_y_range(self) -> None:
|
||||||
|
"""
|
||||||
|
Reset the y-axis range of plots.
|
||||||
|
"""
|
||||||
|
view = self._candle_plot.getViewBox()
|
||||||
|
view_range = view.viewRange()
|
||||||
|
min_ix = max(0, int(view_range[0][0]))
|
||||||
|
max_ix = min(self._manager.get_count(), int(view_range[0][1]))
|
||||||
|
|
||||||
|
price_range = self._manager.get_price_range(min_ix, max_ix)
|
||||||
|
self._candle_plot.setRange(yRange=price_range)
|
||||||
|
|
||||||
|
volume_range = self._manager.get_volume_range(min_ix, max_ix)
|
||||||
|
self._volume_plot.setRange(yRange=volume_range)
|
||||||
|
|
||||||
|
def paintEvent(self, event: QtGui.QPaintEvent) -> None:
|
||||||
|
"""
|
||||||
|
Reimplement this method of parent to update current max_ix value.
|
||||||
|
"""
|
||||||
|
view = self._candle_plot.getViewBox()
|
||||||
|
view_range = view.viewRange()
|
||||||
|
self._max_ix = max(0, view_range[0][1])
|
||||||
|
|
||||||
|
super().paintEvent(event)
|
@ -22,6 +22,9 @@ def extract_vt_symbol(vt_symbol: str):
|
|||||||
|
|
||||||
|
|
||||||
def generate_vt_symbol(symbol: str, exchange: Exchange):
|
def generate_vt_symbol(symbol: str, exchange: Exchange):
|
||||||
|
"""
|
||||||
|
return vt_symbol
|
||||||
|
"""
|
||||||
return f"{symbol}.{exchange.value}"
|
return f"{symbol}.{exchange.value}"
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user