python kline

# -*- coding: utf-8 -*-


# Qt相關和十字光標
from qtpy.QtGui import *
from qtpy.QtCore import *
from qtpy import QtWidgets, QtGui, QtCore
from qtpy.QtWidgets import QApplication, QWidget
from uiCrosshair import Crosshair
import pyqtgraph as pg
# 其餘
import numpy as np
import pandas as pd
from functools import partial
from datetime import datetime, timedelta
from collections import deque

# 字符串轉換
# ---------------------------------------------------------------------------------------
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s


########################################################################
# 鍵盤鼠標功能
########################################################################
class KeyWraper(QtWidgets.QWidget):
    """鍵盤鼠標功能支持的元類"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

    # 重載方法keyPressEvent(self,event),即按鍵按下事件方法
    # ----------------------------------------------------------------------
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            self.onUp()
        elif event.key() == QtCore.Qt.Key_Down:
            self.onDown()
        elif event.key() == QtCore.Qt.Key_Left:
            self.onLeft()
        elif event.key() == QtCore.Qt.Key_Right:
            self.onRight()
        elif event.key() == QtCore.Qt.Key_PageUp:
            self.onPre()
        elif event.key() == QtCore.Qt.Key_PageDown:
            self.onNxt()

    # 重載方法mousePressEvent(self,event),即鼠標點擊事件方法
    # ----------------------------------------------------------------------
    def mousePressEvent(self, event):

        if event.button() == QtCore.Qt.RightButton:
            self.onRClick(event.pos())
        elif event.button() == QtCore.Qt.LeftButton:
            self.onLClick(event.pos())

    # 重載方法mouseReleaseEvent(self,event),即鼠標點擊事件方法
    # ----------------------------------------------------------------------
    def mouseReleaseEvent(self, event):

        if event.button() == QtCore.Qt.RightButton:
            self.onRRelease(event.pos())
        elif event.button() == QtCore.Qt.LeftButton:
            self.onLRelease(event.pos())
        self.releaseMouse()

    # 重載方法wheelEvent(self,event),即滾輪事件方法
    # ----------------------------------------------------------------------
    def wheelEvent(self, event):

        if event.delta() > 0:
            self.onUp()
        else:
            self.onDown()

    # 重載方法dragMoveEvent(self,event),即拖動事件方法
    # ----------------------------------------------------------------------
    def paintEvent(self, event):
        self.onPaint()

    # PgDown鍵
    # ----------------------------------------------------------------------
    def onNxt(self):
        pass

    # PgUp鍵
    # ----------------------------------------------------------------------
    def onPre(self):
        pass

    # 向上鍵和滾輪向上
    # ----------------------------------------------------------------------
    def onUp(self):
        pass

    # 向下鍵和滾輪向下
    # ----------------------------------------------------------------------
    def onDown(self):
        pass

    # 向左鍵
    # ----------------------------------------------------------------------
    def onLeft(self):
        pass

    # 向右鍵
    # ----------------------------------------------------------------------
    def onRight(self):
        pass

    # 鼠標左單擊
    # ----------------------------------------------------------------------
    def onLClick(self, pos):
        pass

    # 鼠標右單擊
    # ----------------------------------------------------------------------
    def onRClick(self, pos):
        pass

    # 鼠標左釋放
    # ----------------------------------------------------------------------
    def onLRelease(self, pos):
        pass

    # 鼠標右釋放
    # ----------------------------------------------------------------------
    def onRRelease(self, pos):
        pass

    # 畫圖
    # ----------------------------------------------------------------------
    def onPaint(self):
        pass


########################################################################
# 選擇縮放功能支持
########################################################################
class CustomViewBox(pg.ViewBox):
    # ----------------------------------------------------------------------
    def __init__(self, parent, *args, **kwds):
        pg.ViewBox.__init__(self, *args, **kwds)
        self.parent = parent
        # 拖動放大模式
        # self.setMouseMode(self.RectMode)

    ## 右鍵自適應
    # ----------------------------------------------------------------------
    def mouseClickEvent(self, ev):

        if ev.button() == QtCore.Qt.RightButton:
            self.autoRange()

    # 重載方法mousePressEvent(self,event),即鼠標點擊事件方法
    # ----------------------------------------------------------------------
    def mousePressEvent(self, event):

        pg.ViewBox.mousePressEvent(self, event)

    # 重載方法mouseDragEvent(self,event),即拖動事件方法
    def mouseDragEvent(self, ev, axis=None):
        # if ev.start==True and ev.finish==False: ##判斷拖拽事件是否結束
        pos = ev.pos()
        lastPos = ev.lastPos()
        dif = pos - lastPos

        rect = self.sceneBoundingRect()

        pianyi = dif.x() * self.parent.countK * 2 / rect.width()

        self.parent.index -= int(pianyi)
        self.parent.index = max(self.parent.index, 60)
        xMax = self.parent.index + self.parent.countK  ##
        xMin = self.parent.index - self.parent.countK
        if xMin < 0:
            xMin = 0

        # self.parent.plotAll(False, xMin, xMax) #註釋緣由:拖動事件不須要先繪製圖形界面

        pg.ViewBox.mouseDragEvent(self, ev, axis)
    # ## 重載方法resizeEvent(self, ev)

    def resizeEvent(self, ev):
        self.linkedXChanged()
        self.linkedYChanged()
        self.updateAutoRange()
        self.updateViewRange()
        self._matrixNeedsUpdate = True
        self.sigStateChanged.emit(self)
        self.background.setRect(self.rect())
        self.sigResized.emit(self)
        self.parent.refreshHeight()


########################################################################
# 時間序列,橫座標支持
########################################################################
class MyStringAxis(pg.AxisItem):
    """時間序列橫座標支持"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, xdict, *args, **kwargs):
        pg.AxisItem.__init__(self, *args, **kwargs)
        self.minVal = 0
        self.maxVal = 0
        self.xdict = xdict
        self.x_values = np.asarray(xdict.keys())
        self.x_strings = xdict.values()
        self.setPen(color=(255, 255, 255, 255), width=0.8)
        self.setStyle(tickFont=QFont("Roman times", 10, QFont.Bold), autoExpandTextSpace=True)

    # 更新座標映射表
    # ----------------------------------------------------------------------
    def update_xdict(self, xdict):
        self.xdict.update(xdict)
        self.x_values = np.asarray(self.xdict.keys())
        self.x_strings = self.xdict.values()

    # 將原始橫座標轉換爲時間字符串,第一個座標包含日期
    # ----------------------------------------------------------------------
    def tickStrings(self, values, scale, spacing):
        strings = []
        for v in values:
            vs = v * scale
            if vs in self.x_values:
                vstr = self.x_strings[np.abs(self.x_values - vs).argmin()]
                if (isinstance(vstr, (str))):
                    vstr = vstr
                else:
                    vstr = vstr.strftime('%Y-%m-%d %H:%M:%S')
            else:
                vstr = ""
            strings.append(vstr)
        return strings


########################################################################
# K線圖形對象
########################################################################
class CandlestickItem(pg.GraphicsObject):
    """K線圖形對象"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, data):

        """初始化"""
        pg.GraphicsObject.__init__(self)

        # 數據格式: [ (time, open, close, low, high),...]
        self.data = data
        # 只重畫部分圖形,大大提升界面更新速度
        self.setFlag(self.ItemUsesExtendedStyleOption)
        # 畫筆和畫刷
        w = 0.4
        self.offset = 0
        self.low = 0
        self.high = 1
        self.picture = QtGui.QPicture()
        self.pictures = []
        self.bPen = pg.mkPen(color=(0, 240, 240, 255), width=w * 2)
        self.bBrush = pg.mkBrush((0, 240, 240, 255))
        self.rPen = pg.mkPen(color=(255, 60, 60, 255), width=w * 2)
        self.rBrush = pg.mkBrush((255, 60, 60, 255))
        self.rBrush.setStyle(Qt.NoBrush)
        # 刷新K線
        self.generatePicture(self.data)

    # 畫K線
    # ----------------------------------------------------------------------
    def generatePicture(self, data=None, redraw=False):
        """從新生成圖形對象"""
        # 重畫或者只更新最後一個K線
        if redraw:
            self.pictures = []
        elif self.pictures:
            self.pictures.pop()
        w = 0.4
        bPen = self.bPen
        bBrush = self.bBrush
        rPen = self.rPen
        rBrush = self.rBrush
        low, high = (data[0]['low'], data[0]['high']) if len(data) > 0 else (0, 1)
        for (t, open0, close0, low0, high0) in data:
            if t >= len(self.pictures):

                tShift = t

                low, high = (min(low, low0), max(high, high0))
                picture = QtGui.QPicture()
                p = QtGui.QPainter(picture)
                # # 下跌藍色(實心), 上漲紅色(空心)
                pen, brush, pmin, pmax = (bPen, bBrush, close0, open0) \
                    if open0 > close0 else (rPen, rBrush, open0, close0)
                p.setPen(pen)
                p.setBrush(brush)
                # 畫K線方塊和上下影線
                if pmin > low0:
                    p.drawLine(QtCore.QPointF(tShift, low0), QtCore.QPointF(tShift, pmin))
                if high0 > pmax:
                    p.drawLine(QtCore.QPointF(tShift, pmax), QtCore.QPointF(tShift, high0))
                p.drawRect(QtCore.QRectF(tShift - w, open0, w * 2, close0 - open0))
                # if open0 == close0:
                #     p.drawRect(QtCore.QPointF(tShift - w, open0), QtCore.QPointF(tShift + w, close0))
                # else:
                #     p.drawRect(QtCore.QRectF(tShift - w, open0, w * 2, close0 - open0))
                # if pmin > low0:
                #     p.drawLine(QtCore.QPointF(tShift, low0), QtCore.QPointF(tShift, pmin))
                # if high0 > pmax:
                #     p.drawLine(QtCore.QPointF(tShift, pmax), QtCore.QPointF(tShift, high0))
                p.end()

                self.pictures.append(picture)
        self.low, self.high = low, high

    # 手動重畫
    # ----------------------------------------------------------------------
    def update(self):
        if not self.scene() is None:
            self.scene().update()

    # 自動重畫
    # ----------------------------------------------------------------------
    def paint(self, p, o, w):
        rect = o.exposedRect
        xmin, xmax = (max(0, int(rect.left())), min(len(self.pictures), int(rect.right())))

        [p.drawPicture(0, 0, pic) for pic in self.pictures[xmin:xmax]]

    # 定義邊界
    # ----------------------------------------------------------------------
    def boundingRect(self):
        return QtCore.QRectF(0, self.low, len(self.pictures), (self.high - self.low))


########################################################################
class KLineWidget(KeyWraper):
    """用於顯示價格走勢圖"""

    # 保存K線數據的列表和Numpy Array對象
    listBar = []
    listVol = []
    listHigh = []
    listLow = []
    listSig = []
    listOpenInterest = []
    arrows = []

    # 是否完成了歷史數據的讀取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, name=None):
        """Constructor"""
        self.parent = parent
        self.name = name
        super(KLineWidget, self).__init__(parent)

        # 當前序號
        self.index = None  # 下標
        self.countK = 60  # 顯示的K線範圍
        self.oldsize=0#rectd的hieght

        # 緩存數據


        self.datas = []
        self.listBar = []
        self.listVol = []
        self.listHigh = []
        self.listLow = []
        self.listSig = []
        self.listOpenInterest = []
        self.arrows = []
        self.sars = []

        # 全部K線上信號圖
        self.allColor = deque(['blue', 'green', 'yellow', 'white'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        # 調用函數
        self.initUi()

    # ----------------------------------------------------------------------
    #  初始化相關
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.setWindowTitle(u'K線工具')
        # 主圖
        self.pw = pg.PlotWidget()
        # 界面佈局
        self.lay_KL = pg.GraphicsLayout(border=(100, 100, 100))
        self.lay_KL.setContentsMargins(10, 10, 10, 10)
        self.lay_KL.setSpacing(0)
        self.lay_KL.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.lay_KL.setZValue(0)
        self.lay_KL.setMinimumHeight(140)
        self.KLtitle = self.lay_KL.addLabel(u'')
        self.pw.setCentralItem(self.lay_KL)
        # 設置橫座標
        xdict = {}
        self.axisTime = MyStringAxis(xdict, orientation='bottom')
        # 初始化子圖
        self.initplotKline()
        self.initplotVol()
        self.initplotOI()
        # 註冊十字光標
        self.crosshair = Crosshair(self.pw, self)
        # 設置界面
        self.vb = QtWidgets.QVBoxLayout()
        self.vb.addWidget(self.pw)
        self.setLayout(self.vb)
        # 初始化完成
        self.initCompleted = True
        self.oldsize=self.rect().height()

        self.customBox = {}

    # ----------------------------------------------------------------------
    def makePI(self, name):
        """生成PlotItem對象"""
        vb = CustomViewBox(self)
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axisTime})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()

        return plotItem

    # ----------------------------------------------------------------------
    def initplotVol(self):
        """初始化成交量子圖"""
        self.pwVol = self.makePI('PlotVol' + self.name)
        self.volume = CandlestickItem(self.listVol)
        self.pwVol.addItem(self.volume)
        self.pwVol.setMaximumHeight((self.rect().height()-30)/4)
        self.pwVol.setMinimumHeight(12)
        self.pwVol.setXLink('PlotOI' + self.name)
        self.pwVol.hideAxis('bottom')
        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwVol)
        self.lay_KL.adjustSize()


    # ----------------------------------------------------------------------
    def initplotKline(self):
        """初始化K線子圖"""
        self.pwKL = self.makePI('PlotKL' + self.name)
        self.candle = CandlestickItem(self.listBar)
        self.pwKL.addItem(self.candle)
        self.pwKL.setXLink('PlotOI' + self.name)
        self.pwKL.hideAxis('bottom')
        self.pwKL.setMinimumHeight((self.rect().height()-30)/3)

        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwKL)

    # ----------------------------------------------------------------------
    def initplotOI(self):
        """初始化持倉量子圖"""
        self.pwOI = self.makePI('PlotOI' + self.name)
        self.curveOI = self.pwOI.plot()
        self.pwOI.setMaximumHeight((self.rect().height() - 30) / 4)
        self.pwOI.setMinimumHeight(20)

        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwOI)

    # ----------------------------------------------------------------------
    #  畫圖相關
    # ----------------------------------------------------------------------
    def plotVol(self, redraw=False, xmin=0, xmax=-1):
        """重畫成交量子圖"""
        if self.initCompleted:
            self.volume.generatePicture(self.listVol[xmin:xmax], redraw)  # 畫成交量子圖

    # ----------------------------------------------------------------------
    def plotKline(self, redraw=False, xmin=0, xmax=-1):
        """重畫K線子圖"""
        if self.initCompleted:
            self.candle.generatePicture(self.listBar[xmin:xmax], redraw)  # 畫K線
            self.plotMark()  # 顯示開平倉信號位置

    # ----------------------------------------------------------------------
    def plotOI(self, xmin=0, xmax=-1):
        """重畫持倉量子圖"""
        if self.initCompleted:
            # self.curveOI.setData(self.listOpenInterest[xmin:xmax]+[0], symbol='o', name="OpenInterest")
            self.curveOI.setData(self.listOpenInterest[xmin:xmax] + [0],
                                 name="OpenInterest" + self.name)  # 去除symbol='o'

    def updateIndicatorSar(self, sarDatas, startIndex):
        """更新sar指標   startIndex  從0到 len -1"""
        if len(sarDatas) == 0:
            return

        while (len(self.sars) > (startIndex - 1)):
            sar = self.sars[-1]
            self.pwKL.removeItem(sar)
            self.sars.remove(sar)

        for i in range(startIndex, len(sarDatas)):
            arrow = pg.ArrowItem(pos=(i, sarDatas[i]), angle=90, tipAngle=5, headLen=2, tailWidth=4, pen={'width': 1},
                                 brush=(0, 0, 255))

            defaultOpts = {
                'pxMode': True,
                'angle': -150,  ## If the angle is 0, the arrow points left
                'pos': (0, 0),
                'headLen': 10,
                'tipAngle': 10,
                'baseAngle': 0,
                'tailLen': None,
                'tailWidth': 3,
                'pen': (200, 200, 200),
                'brush': (255, 0, 0),
            }
            # arrow.setStyle(defaultOpts)

            self.pwKL.addItem(arrow)
            self.sars.append(arrow)

    def addIndicatorSar(self, sarDatas):
        """增長sar指標"""
        if len(sarDatas) == 0:
            return
        for sar in self.sars:
            self.pwKL.removeItem(sar)
        # 畫信號
        for i in range(len(sarDatas)):
            # arrow = pg.ArrowItem(pos=(i, sarDatas[i]),headLen=10,  angle=90, brush=(0, 0, 255))
            arrow = pg.ArrowItem(pos=(i, sarDatas[i]), angle=90, tipAngle=5, headLen=2, tailWidth=4, pen={'width': 1},
                                 brush=(0, 0, 255))

            defaultOpts = {
                'pxMode': True,
                'angle': -150,  ## If the angle is 0, the arrow points left
                'pos': (0, 0),
                'headLen': 10,
                'tipAngle': 10,
                'baseAngle': 0,
                'tailLen': None,
                'tailWidth': 3,
                'pen': (200, 200, 200),
                'brush': (255, 0, 0),
            }
            # arrow.setStyle(defaultOpts)

            self.pwKL.addItem(arrow)
            self.sars.append(arrow)

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信號圖"""
        if sig in self.sigPlots:
            self.pwKL.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pwKL.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信號圖"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]

        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """顯示開平倉信號"""
        # 檢查是否有數據
        if len(self.datas) == 0:
            return
        for arrow in self.arrows:
            self.pwKL.removeItem(arrow)
        # 畫買賣信號
        for i in range(len(self.listSig)):
            # 無信號
            if self.listSig[i] == None:
                continue
            # 買信號
            elif self.listSig[i] != None:
                direction = self.listSig[i]["direction"]
                offset = self.listSig[i]["offset"]
                price = self.listSig[i]["price"]

                if direction == "" and offset == "開倉":
                    # arrow = pg.ArrowItem(pos=(i, price),  angle=-90, brush=(255, 0, 0))
                    arrow = pg.ArrowItem(pos=(i, price), angle=180, tipAngle=60, headLen=8, tailLen=3, tailWidth=5,
                                         pen={'color': 'w', 'width': 1}, brush='r')
                elif direction == "" and offset == "開倉":
                    # arrow = pg.ArrowItem(pos=(i, price),  angle=90, brush=(255, 0, 0))
                    arrow = pg.ArrowItem(pos=(i, price), angle=180, tipAngle=60, headLen=8, tailLen=3, tailWidth=5,
                                         pen={'color': 'w', 'width': 1}, brush='b')
                elif direction == "" and offset == "平倉":
                    # arrow = pg.ArrowItem(pos=(i, price),  angle=-90, brush=(0, 0, 255))
                    arrow = pg.ArrowItem(pos=(i, price), angle=0, tipAngle=40, headLen=8, tailLen=None, tailWidth=8,
                                         pen={'color': 'w', 'width': 1}, brush='y')
                elif direction == "" and offset == "平倉":
                    # arrow = pg.ArrowItem(pos=(i, price),  angle=90, brush=(0, 0, 255))
                    arrow = pg.ArrowItem(pos=(i, price), angle=0, tipAngle=40, headLen=8, tailLen=None, tailWidth=8,
                                         pen={'color': 'w', 'width': 1}, brush='y')
            self.pwKL.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手動更新全部K線圖形,K線播放模式下須要
        """
        datas = self.datas
        self.volume.update()
        self.candle.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                if  ymin and ymax:
                   view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pwKL.getViewBox(), 'low', 'high')
        update(self.pwVol.getViewBox(), 'volume', 'volume')
        #update(self.pwOI.getViewBox(), 'openInterest', 'openInterest')
        update(self.curveOI.getViewBox(), 'openInterest', 'openInterest')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重畫全部界面
        redraw :False=重畫最後一根K線; True=重畫全部
        xMin,xMax : 數據範圍
        """
        # xMax = len(self.datas) if xMax < 0 else xMax
        # self.countK = xMax-xMin
        # self.index = int((xMax+xMin)/2)
        if redraw:
            xmax = len(self.datas) if xMax < 0 else xMax
            xmin=max(0,xmax-self.countK)
            self.index = int((xmax + xmin) / 2)
        self.pwOI.setLimits(xMin=xMin, xMax=xMax)
        self.pwKL.setLimits(xMin=xMin, xMax=xMax)
        self.pwVol.setLimits(xMin=xMin, xMax=xMax)
        self.plotKline(redraw, xMin, xMax)  # K線圖
        self.plotVol(redraw, xMin, xMax)  # K線副圖,成交量
        self.plotOI(0, len(self.datas))  # K線副圖,持倉量
        self.refresh()

    def refreshHeight(self):
       # super.__init__(QResizeEvent)
        # 若是窗口最大化,不修改比例,防護性設計
        # if self.isMaximized():
        #     return
       if len(self.datas)!=0:
            height =self.rect().height()
            if height!=self.oldsize:
                self.oldsize=height
                height=(height-30)/4

                self.pwKL.setMinimumHeight(height * 2-24)
                self.pwVol.setMaximumHeight(height)
                self.pwVol.setMinimumHeight(12)
                self.pwOI.setMaximumHeight(height)
                self.pwOI.setMinimumHeight(12)

                #print height


    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三個子圖的現實範圍
        """
        datas = self.datas
        minutes = int(self.countK / 2)
        xmin = max(0, self.index - minutes)
        xmax = xmin + 2 * minutes
        self.pwOI.setRange(xRange=(xmin, xmax))
        self.pwKL.setRange(xRange=(xmin, xmax))
        self.pwVol.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷鍵相關
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳轉到下一個開平倉點"""
        if len(self.listSig) > 0 and not self.index is None:
            datalen = len(self.listSig)
            self.index += 1
            while self.index < datalen and self.listSig[self.index] == 0:
                self.index += 1
            self.refresh()
            x = self.index
            y = self.datas[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳轉到上一個開平倉點"""
        if len(self.listSig) > 0 and not self.index is None:
            self.index -= 1
            while self.index > 0 and self.listSig[self.index] == 0:
                self.index -= 1
            self.refresh()
            x = self.index
            y = self.datas[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大顯示區間"""
        self.countK = min(len(self.datas), int(self.countK * 1.2) + 1)
        self.refresh()
        if len(self.datas) > 0:
            x = self.index - self.countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.index - self.countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.index + self.countK / 2 - 2 if x > self.index + self.countK / 2 - 2 else x
            x=min(x,len(self.datas)-1)
            y = self.datas[x][2]
            # y = self.datas[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onUp(self):
        """縮小顯示區間"""
        # self.countK = max(3,int(self.countK/1.2)-1)
        self.countK = max(20, int(self.countK / 1.2) - 1)  # 最小顯示k線範圍20
        self.refresh()
        if len(self.datas) > 0:
            x = self.index - self.countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.index - self.countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.index + self.countK / 2 - 2 if x > self.index + self.countK / 2 - 2 else x
            x = min(x, len(self.datas)-1)
            y = self.datas[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移動"""
        if len(self.datas) > 0 and int(self.crosshair.xAxis) > 2:
            x = int(self.crosshair.xAxis) - 1
            y = self.datas[x]['close']
            if x <= self.index - self.countK / 2 + 2 and self.index > 1:
                self.index -= 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移動"""
        if len(self.datas) > 0 and int(self.crosshair.xAxis) < len(self.datas) - 1:
            x = int(self.crosshair.xAxis) + 1
            y = self.datas[x]['close']
            if x >= self.index + int(self.countK / 2) - 2:
                self.index += 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    # 界面回調相關
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回調"""
        view = self.pwKL.getViewBox()
        vRange = view.viewRange()
        # xmin = max(0,int(vRange[0][0]))
        # xmax = max(0,int(vRange[0][1]))
        # self.index  = int((xmin+xmax)/2)+1

    # ----------------------------------------------------------------------
    def resignData(self, datas):
        """更新數據,用於Y座標自適應"""
        self.crosshair.datas = datas

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                if ymin and ymax:
                   self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pwKL.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'low', 'high'))
        view = self.pwVol.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))

        view = self.pwOI.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 數據相關
    # ----------------------------------------------------------------------
    def clearData(self):
        """清空數據"""
        # 清空數據,從新畫圖
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.datas = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新買賣信號"""
        self.listSig = sig
        self.plotMark()
    # ----------------------------------------------------------------------
    def onBar(self, bar, nWindow=20):
        """
        新增K線數據,K線播放模式
        nWindow : 最大數據窗口
        """
        # 是否須要更新K線
        newBar = False if len(self.datas) > 0 and bar.datetime == self.datas[-1].datetime else True
        nrecords = len(self.datas) if newBar else len(self.datas) - 1
        bar.openInterest = np.random.randint(0,
                                             3) if bar.openInterest == np.inf or bar.openInterest == -np.inf else bar.openInterest
        recordVol = (nrecords, bar.volume, 0, 0, bar.volume) if bar.close < bar.open else (
        nrecords, 0, bar.volume, 0, bar.volume)
        if newBar and any(self.datas):
            self.datas.resize(nrecords + 1, refcheck=0)
            self.listBar.resize(nrecords + 1, refcheck=0)
            self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.datas):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.datas):
            self.datas[-1] = (bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)
            self.listBar[-1] = (nrecords, bar.open, bar.close, bar.low, bar.high)
            self.listVol[-1] = recordVol
        else:
            self.datas = np.rec.array(
                [(datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, bar.open, bar.close, bar.low, bar.high)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.datas)
        self.axisTime.update_xdict({nrecords: bar.datetime})
        self.listLow.append(bar.low)
        self.listHigh.append(bar.high)
        self.listOpenInterest.append(bar.openInterest)
        # if newBar:
        #     xMax = nrecords + 1
        #     xMin = max(0, xMax - self.countK)  # 最小顯示區間
        #     self.index=int(xMax+xMin)/2

        # if self.pwKL.getViewBox().mousePress == True:
        # print("sssssssssss")

        # if self.pwKL.getViewBox().mousePress == False: ##self.mousePress==false,不在執行刷新操做



        xMax = self.index + self.countK
        xMin = self.index - self.countK
        #xMin=(0,xMin)
        if xMin<0:
            xMin=0

        if not newBar:
            self.updateAll()

        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    # ----------------------------------------------------------------------
    def loadData(self, datas):
        """
        載入pandas.DataFrame數據
        datas : 數據格式,cols : datetime, open, close, low, high
        """
        # 設置中心點時間
        self.index = 0

        # 綁定數據,更新橫座標映射,更新Y軸自適應函數,更新十字光標映射
        datas['time_int'] = np.array(range(len(datas.index)))
        self.datas = datas[['open', 'close', 'low', 'high', 'volume', 'openInterest']].to_records()
        self.axisTime.xdict = {}
        xdict = dict(enumerate(datas.index.tolist()))
        self.axisTime.update_xdict(xdict)
        self.resignData(self.datas)
        # 更新畫圖用到的數據
        self.listBar = datas[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        self.listHigh = list(datas['high'])
        self.listLow = list(datas['low'])
        self.listOpenInterest = list(datas['openInterest'])
        # 成交量顏色和漲跌同步,K線方向由漲跌決定
        datas0 = pd.DataFrame()
        datas0['open'] = datas.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
        datas0['close'] = datas.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
        datas0['low'] = datas0['open']
        datas0['high'] = datas0['close']
        datas0['time_int'] = np.array(range(len(datas.index)))
        self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        # 調用畫圖函數
        self.plotAll(True, 0, len(self.datas))

    def loadDataBar(self, datas):
        """
        載入pandas.DataFrame數據
        datas : 數據格式,'symbol','vtSymbol','exchange','open','high','low','close','date','time1','datetime','volume','openInterest'
        """
        print(datas)
        self.index = 0

        # 綁定數據,更新橫座標映射,更新Y軸自適應函數,更新十字光標映射
        datas['time_int'] = np.array(range(len(datas.index)))

        self.datas = datas[['datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest']].to_records(False)
        # self.datas = datas[['symbol','vtSymbol','exchange','open','high','low','close','date','time1','datetime','volume','openInterest']].to_records()
        self.axisTime.xdict = {}
        # xdict = dict(enumerate(datas.index.tolist()))
        xdict = dict(enumerate(datas['datetime'].tolist()))
        self.axisTime.update_xdict(xdict)
        self.resignData(self.datas)
        # 更新畫圖用到的數據
        self.listBar = datas[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        self.listHigh = list(datas['high'])
        self.listLow = list(datas['low'])
        self.listOpenInterest = list(datas['openInterest'])
        # 成交量顏色和漲跌同步,K線方向由漲跌決定
        datas0 = pd.DataFrame()
        datas0['open'] = datas.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
        datas0['close'] = datas.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
        datas0['low'] = datas0['open']
        datas0['high'] = datas0['close']
        datas0['time_int'] = np.array(range(len(datas.index)))
        self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        # 調用畫圖函數
        self.plotAll(True, 0, len(self.datas))

    ##VtBarData 結構
    def loadDataBarArray(self, record):
        txtData = pd.DataFrame.from_records(record, index="datetime")
        # txtData = txtData.rename(columns = {0:'symbol', 1:"vtSymbol",2:"exchange",3:"open",4:"high",5:"low",6:"close",7:"date",8:"time",9:"datetime",10:"volume",11:"openInterest"})

        self.loadData(txtData)


########################################################################
# 功能測試
########################################################################
import sys

if __name__ == '__main__':
    app = QApplication(sys.argv)
    # 界面設置
    cfgfile = QtCore.QFile('css.qss')
    cfgfile.open(QtCore.QFile.ReadOnly)
    styleSheet = cfgfile.readAll()
    styleSheet = unicode(styleSheet, encoding='utf8')
    app.setStyleSheet(styleSheet)
    # K線界面
    # ui = KLineWidget()
    # ui.show()
    # ui.KLtitle.setText('rb1701',size='20pt')
    # ui.loadData(pd.DataFrame.from_csv('data.csv'))

    ui = KLineWidget(name="opt")
    ui.show()
    ui.KLtitle.setText('rb1701', size='20pt')

    # txtData = pd.DataFrame.from_csv('D:/data/day/rb1101.txt',header=None,index_col=7)
    txtData = pd.DataFrame.from_csv('E:/Data/day/rb1101.txt', header=None, index_col=7)



    txtData = txtData.rename(
        columns={0: 'symbol', 1: "vtSymbol", 2: "exchange", 3: "open", 4: "high", 5: "low", 6: "close", 7: "date",
                 8: "time", 9: "datetime", 10: "volume", 11: "openInterest"})

    ui.loadDataBar(txtData)
    app.exec_()
相關文章
相關標籤/搜索