1. # -*- coding: utf-8 -*-
  2.  
  3. # Qt相关和十字光标
  4. from qtpy.QtGui import *
  5. from qtpy.QtCore import *
  6. from qtpy import QtWidgets, QtGui, QtCore
  7. from qtpy.QtWidgets import QApplication, QWidget
  8. from uiCrosshair import Crosshair
  9. import pyqtgraph as pg
  10. # 其他
  11. import numpy as np
  12. import pandas as pd
  13. from functools import partial
  14. from datetime import datetime, timedelta
  15. from collections import deque
  16.  
  17. # 字符串转换
  18. # ---------------------------------------------------------------------------------------
  19. try:
  20. _fromUtf8 = QtCore.QString.fromUtf8
  21. except AttributeError:
  22. def _fromUtf8(s):
  23. return s
  24.  
  25. ########################################################################
  26. # 键盘鼠标功能
  27. ########################################################################
  28. class KeyWraper(QtWidgets.QWidget):
  29. """键盘鼠标功能支持的元类"""
  30.  
  31. # 初始化
  32. # ----------------------------------------------------------------------
  33. def __init__(self, parent=None):
  34. QtWidgets.QWidget.__init__(self, parent)
  35.  
  36. # 重载方法keyPressEvent(self,event),即按键按下事件方法
  37. # ----------------------------------------------------------------------
  38. def keyPressEvent(self, event):
  39. if event.key() == QtCore.Qt.Key_Up:
  40. self.onUp()
  41. elif event.key() == QtCore.Qt.Key_Down:
  42. self.onDown()
  43. elif event.key() == QtCore.Qt.Key_Left:
  44. self.onLeft()
  45. elif event.key() == QtCore.Qt.Key_Right:
  46. self.onRight()
  47. elif event.key() == QtCore.Qt.Key_PageUp:
  48. self.onPre()
  49. elif event.key() == QtCore.Qt.Key_PageDown:
  50. self.onNxt()
  51.  
  52. # 重载方法mousePressEvent(self,event),即鼠标点击事件方法
  53. # ----------------------------------------------------------------------
  54. def mousePressEvent(self, event):
  55.  
  56. if event.button() == QtCore.Qt.RightButton:
  57. self.onRClick(event.pos())
  58. elif event.button() == QtCore.Qt.LeftButton:
  59. self.onLClick(event.pos())
  60.  
  61. # 重载方法mouseReleaseEvent(self,event),即鼠标点击事件方法
  62. # ----------------------------------------------------------------------
  63. def mouseReleaseEvent(self, event):
  64.  
  65. if event.button() == QtCore.Qt.RightButton:
  66. self.onRRelease(event.pos())
  67. elif event.button() == QtCore.Qt.LeftButton:
  68. self.onLRelease(event.pos())
  69. self.releaseMouse()
  70.  
  71. # 重载方法wheelEvent(self,event),即滚轮事件方法
  72. # ----------------------------------------------------------------------
  73. def wheelEvent(self, event):
  74.  
  75. if event.delta() > 0:
  76. self.onUp()
  77. else:
  78. self.onDown()
  79.  
  80. # 重载方法dragMoveEvent(self,event),即拖动事件方法
  81. # ----------------------------------------------------------------------
  82. def paintEvent(self, event):
  83. self.onPaint()
  84.  
  85. # PgDown键
  86. # ----------------------------------------------------------------------
  87. def onNxt(self):
  88. pass
  89.  
  90. # PgUp键
  91. # ----------------------------------------------------------------------
  92. def onPre(self):
  93. pass
  94.  
  95. # 向上键和滚轮向上
  96. # ----------------------------------------------------------------------
  97. def onUp(self):
  98. pass
  99.  
  100. # 向下键和滚轮向下
  101. # ----------------------------------------------------------------------
  102. def onDown(self):
  103. pass
  104.  
  105. # 向左键
  106. # ----------------------------------------------------------------------
  107. def onLeft(self):
  108. pass
  109.  
  110. # 向右键
  111. # ----------------------------------------------------------------------
  112. def onRight(self):
  113. pass
  114.  
  115. # 鼠标左单击
  116. # ----------------------------------------------------------------------
  117. def onLClick(self, pos):
  118. pass
  119.  
  120. # 鼠标右单击
  121. # ----------------------------------------------------------------------
  122. def onRClick(self, pos):
  123. pass
  124.  
  125. # 鼠标左释放
  126. # ----------------------------------------------------------------------
  127. def onLRelease(self, pos):
  128. pass
  129.  
  130. # 鼠标右释放
  131. # ----------------------------------------------------------------------
  132. def onRRelease(self, pos):
  133. pass
  134.  
  135. # 画图
  136. # ----------------------------------------------------------------------
  137. def onPaint(self):
  138. pass
  139.  
  140. ########################################################################
  141. # 选择缩放功能支持
  142. ########################################################################
  143. class CustomViewBox(pg.ViewBox):
  144. # ----------------------------------------------------------------------
  145. def __init__(self, parent, *args, **kwds):
  146. pg.ViewBox.__init__(self, *args, **kwds)
  147. self.parent = parent
  148. # 拖动放大模式
  149. # self.setMouseMode(self.RectMode)
  150.  
  151. ## 右键自适应
  152. # ----------------------------------------------------------------------
  153. def mouseClickEvent(self, ev):
  154.  
  155. if ev.button() == QtCore.Qt.RightButton:
  156. self.autoRange()
  157.  
  158. # 重载方法mousePressEvent(self,event),即鼠标点击事件方法
  159. # ----------------------------------------------------------------------
  160. def mousePressEvent(self, event):
  161.  
  162. pg.ViewBox.mousePressEvent(self, event)
  163.  
  164. # 重载方法mouseDragEvent(self,event),即拖动事件方法
  165. def mouseDragEvent(self, ev, axis=None):
  166. # if ev.start==True and ev.finish==False: ##判断拖拽事件是否结束
  167. pos = ev.pos()
  168. lastPos = ev.lastPos()
  169. dif = pos - lastPos
  170.  
  171. rect = self.sceneBoundingRect()
  172.  
  173. pianyi = dif.x() * self.parent.countK * 2 / rect.width()
  174.  
  175. self.parent.index -= int(pianyi)
  176. self.parent.index = max(self.parent.index, 60)
  177. xMax = self.parent.index + self.parent.countK ##
  178. xMin = self.parent.index - self.parent.countK
  179. if xMin < 0:
  180. xMin = 0
  181.  
  182. # self.parent.plotAll(False, xMin, xMax) #注释原因:拖动事件不需要先绘制图形界面
  183.  
  184. pg.ViewBox.mouseDragEvent(self, ev, axis)
  185. # ## 重载方法resizeEvent(self, ev)
  186.  
  187. def resizeEvent(self, ev):
  188. self.linkedXChanged()
  189. self.linkedYChanged()
  190. self.updateAutoRange()
  191. self.updateViewRange()
  192. self._matrixNeedsUpdate = True
  193. self.sigStateChanged.emit(self)
  194. self.background.setRect(self.rect())
  195. self.sigResized.emit(self)
  196. self.parent.refreshHeight()
  197.  
  198. ########################################################################
  199. # 时间序列,横坐标支持
  200. ########################################################################
  201. class MyStringAxis(pg.AxisItem):
  202. """时间序列横坐标支持"""
  203.  
  204. # 初始化
  205. # ----------------------------------------------------------------------
  206. def __init__(self, xdict, *args, **kwargs):
  207. pg.AxisItem.__init__(self, *args, **kwargs)
  208. self.minVal = 0
  209. self.maxVal = 0
  210. self.xdict = xdict
  211. self.x_values = np.asarray(xdict.keys())
  212. self.x_strings = xdict.values()
  213. self.setPen(color=(255, 255, 255, 255), width=0.8)
  214. self.setStyle(tickFont=QFont("Roman times", 10, QFont.Bold), autoExpandTextSpace=True)
  215.  
  216. # 更新坐标映射表
  217. # ----------------------------------------------------------------------
  218. def update_xdict(self, xdict):
  219. self.xdict.update(xdict)
  220. self.x_values = np.asarray(self.xdict.keys())
  221. self.x_strings = self.xdict.values()
  222.  
  223. # 将原始横坐标转换为时间字符串,第一个坐标包含日期
  224. # ----------------------------------------------------------------------
  225. def tickStrings(self, values, scale, spacing):
  226. strings = []
  227. for v in values:
  228. vs = v * scale
  229. if vs in self.x_values:
  230. vstr = self.x_strings[np.abs(self.x_values - vs).argmin()]
  231. if (isinstance(vstr, (str))):
  232. vstr = vstr
  233. else:
  234. vstr = vstr.strftime('%Y-%m-%d %H:%M:%S')
  235. else:
  236. vstr = ""
  237. strings.append(vstr)
  238. return strings
  239.  
  240. ########################################################################
  241. # K线图形对象
  242. ########################################################################
  243. class CandlestickItem(pg.GraphicsObject):
  244. """K线图形对象"""
  245.  
  246. # 初始化
  247. # ----------------------------------------------------------------------
  248. def __init__(self, data):
  249.  
  250. """初始化"""
  251. pg.GraphicsObject.__init__(self)
  252.  
  253. # 数据格式: [ (time, open, close, low, high),...]
  254. self.data = data
  255. # 只重画部分图形,大大提高界面更新速度
  256. self.setFlag(self.ItemUsesExtendedStyleOption)
  257. # 画笔和画刷
  258. w = 0.4
  259. self.offset = 0
  260. self.low = 0
  261. self.high = 1
  262. self.picture = QtGui.QPicture()
  263. self.pictures = []
  264. self.bPen = pg.mkPen(color=(0, 240, 240, 255), width=w * 2)
  265. self.bBrush = pg.mkBrush((0, 240, 240, 255))
  266. self.rPen = pg.mkPen(color=(255, 60, 60, 255), width=w * 2)
  267. self.rBrush = pg.mkBrush((255, 60, 60, 255))
  268. self.rBrush.setStyle(Qt.NoBrush)
  269. # 刷新K线
  270. self.generatePicture(self.data)
  271.  
  272. # 画K线
  273. # ----------------------------------------------------------------------
  274. def generatePicture(self, data=None, redraw=False):
  275. """重新生成图形对象"""
  276. # 重画或者只更新最后一个K线
  277. if redraw:
  278. self.pictures = []
  279. elif self.pictures:
  280. self.pictures.pop()
  281. w = 0.4
  282. bPen = self.bPen
  283. bBrush = self.bBrush
  284. rPen = self.rPen
  285. rBrush = self.rBrush
  286. low, high = (data[0]['low'], data[0]['high']) if len(data) > 0 else (0, 1)
  287. for (t, open0, close0, low0, high0) in data:
  288. if t >= len(self.pictures):
  289.  
  290. tShift = t
  291.  
  292. low, high = (min(low, low0), max(high, high0))
  293. picture = QtGui.QPicture()
  294. p = QtGui.QPainter(picture)
  295. # # 下跌蓝色(实心), 上涨红色(空心)
  296. pen, brush, pmin, pmax = (bPen, bBrush, close0, open0) \
  297. if open0 > close0 else (rPen, rBrush, open0, close0)
  298. p.setPen(pen)
  299. p.setBrush(brush)
  300. # 画K线方块和上下影线
  301. if pmin > low0:
  302. p.drawLine(QtCore.QPointF(tShift, low0), QtCore.QPointF(tShift, pmin))
  303. if high0 > pmax:
  304. p.drawLine(QtCore.QPointF(tShift, pmax), QtCore.QPointF(tShift, high0))
  305. p.drawRect(QtCore.QRectF(tShift - w, open0, w * 2, close0 - open0))
  306. # if open0 == close0:
  307. # p.drawRect(QtCore.QPointF(tShift - w, open0), QtCore.QPointF(tShift + w, close0))
  308. # else:
  309. # p.drawRect(QtCore.QRectF(tShift - w, open0, w * 2, close0 - open0))
  310. # if pmin > low0:
  311. # p.drawLine(QtCore.QPointF(tShift, low0), QtCore.QPointF(tShift, pmin))
  312. # if high0 > pmax:
  313. # p.drawLine(QtCore.QPointF(tShift, pmax), QtCore.QPointF(tShift, high0))
  314. p.end()
  315.  
  316. self.pictures.append(picture)
  317. self.low, self.high = low, high
  318.  
  319. # 手动重画
  320. # ----------------------------------------------------------------------
  321. def update(self):
  322. if not self.scene() is None:
  323. self.scene().update()
  324.  
  325. # 自动重画
  326. # ----------------------------------------------------------------------
  327. def paint(self, p, o, w):
  328. rect = o.exposedRect
  329. xmin, xmax = (max(0, int(rect.left())), min(len(self.pictures), int(rect.right())))
  330.  
  331. [p.drawPicture(0, 0, pic) for pic in self.pictures[xmin:xmax]]
  332.  
  333. # 定义边界
  334. # ----------------------------------------------------------------------
  335. def boundingRect(self):
  336. return QtCore.QRectF(0, self.low, len(self.pictures), (self.high - self.low))
  337.  
  338. ########################################################################
  339. class KLineWidget(KeyWraper):
  340. """用于显示价格走势图"""
  341.  
  342. # 保存K线数据的列表和Numpy Array对象
  343. listBar = []
  344. listVol = []
  345. listHigh = []
  346. listLow = []
  347. listSig = []
  348. listOpenInterest = []
  349. arrows = []
  350.  
  351. # 是否完成了历史数据的读取
  352. initCompleted = False
  353.  
  354. # ----------------------------------------------------------------------
  355. def __init__(self, parent=None, name=None):
  356. """Constructor"""
  357. self.parent = parent
  358. self.name = name
  359. super(KLineWidget, self).__init__(parent)
  360.  
  361. # 当前序号
  362. self.index = None # 下标
  363. self.countK = 60 # 显示的K线范围
  364. self.oldsize=0#rectd的hieght
  365.  
  366. # 缓存数据
  367.  
  368. self.datas = []
  369. self.listBar = []
  370. self.listVol = []
  371. self.listHigh = []
  372. self.listLow = []
  373. self.listSig = []
  374. self.listOpenInterest = []
  375. self.arrows = []
  376. self.sars = []
  377.  
  378. # 所有K线上信号图
  379. self.allColor = deque(['blue', 'green', 'yellow', 'white'])
  380. self.sigData = {}
  381. self.sigColor = {}
  382. self.sigPlots = {}
  383.  
  384. # 初始化完成
  385. self.initCompleted = False
  386.  
  387. # 调用函数
  388. self.initUi()
  389.  
  390. # ----------------------------------------------------------------------
  391. # 初始化相关
  392. # ----------------------------------------------------------------------
  393. def initUi(self):
  394. """初始化界面"""
  395. self.setWindowTitle(u'K线工具')
  396. # 主图
  397. self.pw = pg.PlotWidget()
  398. # 界面布局
  399. self.lay_KL = pg.GraphicsLayout(border=(100, 100, 100))
  400. self.lay_KL.setContentsMargins(10, 10, 10, 10)
  401. self.lay_KL.setSpacing(0)
  402. self.lay_KL.setBorder(color=(255, 255, 255, 255), width=0.8)
  403. self.lay_KL.setZValue(0)
  404. self.lay_KL.setMinimumHeight(140)
  405. self.KLtitle = self.lay_KL.addLabel(u'')
  406. self.pw.setCentralItem(self.lay_KL)
  407. # 设置横坐标
  408. xdict = {}
  409. self.axisTime = MyStringAxis(xdict, orientation='bottom')
  410. # 初始化子图
  411. self.initplotKline()
  412. self.initplotVol()
  413. self.initplotOI()
  414. # 注册十字光标
  415. self.crosshair = Crosshair(self.pw, self)
  416. # 设置界面
  417. self.vb = QtWidgets.QVBoxLayout()
  418. self.vb.addWidget(self.pw)
  419. self.setLayout(self.vb)
  420. # 初始化完成
  421. self.initCompleted = True
  422. self.oldsize=self.rect().height()
  423.  
  424. self.customBox = {}
  425.  
  426. # ----------------------------------------------------------------------
  427. def makePI(self, name):
  428. """生成PlotItem对象"""
  429. vb = CustomViewBox(self)
  430. plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axisTime})
  431. plotItem.setMenuEnabled(False)
  432. plotItem.setClipToView(True)
  433. plotItem.hideAxis('left')
  434. plotItem.showAxis('right')
  435. plotItem.setDownsampling(mode='peak')
  436. plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
  437. plotItem.getAxis('right').setWidth(60)
  438. plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
  439. plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
  440. plotItem.showGrid(True, True)
  441. plotItem.hideButtons()
  442.  
  443. return plotItem
  444.  
  445. # ----------------------------------------------------------------------
  446. def initplotVol(self):
  447. """初始化成交量子图"""
  448. self.pwVol = self.makePI('PlotVol' + self.name)
  449. self.volume = CandlestickItem(self.listVol)
  450. self.pwVol.addItem(self.volume)
  451. self.pwVol.setMaximumHeight((self.rect().height()-30)/4)
  452. self.pwVol.setMinimumHeight(12)
  453. self.pwVol.setXLink('PlotOI' + self.name)
  454. self.pwVol.hideAxis('bottom')
  455. self.lay_KL.nextRow()
  456. self.lay_KL.addItem(self.pwVol)
  457. self.lay_KL.adjustSize()
  458.  
  459. # ----------------------------------------------------------------------
  460. def initplotKline(self):
  461. """初始化K线子图"""
  462. self.pwKL = self.makePI('PlotKL' + self.name)
  463. self.candle = CandlestickItem(self.listBar)
  464. self.pwKL.addItem(self.candle)
  465. self.pwKL.setXLink('PlotOI' + self.name)
  466. self.pwKL.hideAxis('bottom')
  467. self.pwKL.setMinimumHeight((self.rect().height()-30)/3)
  468.  
  469. self.lay_KL.nextRow()
  470. self.lay_KL.addItem(self.pwKL)
  471.  
  472. # ----------------------------------------------------------------------
  473. def initplotOI(self):
  474. """初始化持仓量子图"""
  475. self.pwOI = self.makePI('PlotOI' + self.name)
  476. self.curveOI = self.pwOI.plot()
  477. self.pwOI.setMaximumHeight((self.rect().height() - 30) / 4)
  478. self.pwOI.setMinimumHeight(20)
  479.  
  480. self.lay_KL.nextRow()
  481. self.lay_KL.addItem(self.pwOI)
  482.  
  483. # ----------------------------------------------------------------------
  484. # 画图相关
  485. # ----------------------------------------------------------------------
  486. def plotVol(self, redraw=False, xmin=0, xmax=-1):
  487. """重画成交量子图"""
  488. if self.initCompleted:
  489. self.volume.generatePicture(self.listVol[xmin:xmax], redraw) # 画成交量子图
  490.  
  491. # ----------------------------------------------------------------------
  492. def plotKline(self, redraw=False, xmin=0, xmax=-1):
  493. """重画K线子图"""
  494. if self.initCompleted:
  495. self.candle.generatePicture(self.listBar[xmin:xmax], redraw) # 画K线
  496. self.plotMark() # 显示开平仓信号位置
  497.  
  498. # ----------------------------------------------------------------------
  499. def plotOI(self, xmin=0, xmax=-1):
  500. """重画持仓量子图"""
  501. if self.initCompleted:
  502. # self.curveOI.setData(self.listOpenInterest[xmin:xmax]+[0], symbol='o', name="OpenInterest")
  503. self.curveOI.setData(self.listOpenInterest[xmin:xmax] + [0],
  504. name="OpenInterest" + self.name) # 去除symbol='o'
  505.  
  506. def updateIndicatorSar(self, sarDatas, startIndex):
  507. """更新sar指标 startIndex 从0到 len -1"""
  508. if len(sarDatas) == 0:
  509. return
  510.  
  511. while (len(self.sars) > (startIndex - 1)):
  512. sar = self.sars[-1]
  513. self.pwKL.removeItem(sar)
  514. self.sars.remove(sar)
  515.  
  516. for i in range(startIndex, len(sarDatas)):
  517. arrow = pg.ArrowItem(pos=(i, sarDatas[i]), angle=90, tipAngle=5, headLen=2, tailWidth=4, pen={'width': 1},
  518. brush=(0, 0, 255))
  519.  
  520. defaultOpts = {
  521. 'pxMode': True,
  522. 'angle': -150, ## If the angle is 0, the arrow points left
  523. 'pos': (0, 0),
  524. 'headLen': 10,
  525. 'tipAngle': 10,
  526. 'baseAngle': 0,
  527. 'tailLen': None,
  528. 'tailWidth': 3,
  529. 'pen': (200, 200, 200),
  530. 'brush': (255, 0, 0),
  531. }
  532. # arrow.setStyle(defaultOpts)
  533.  
  534. self.pwKL.addItem(arrow)
  535. self.sars.append(arrow)
  536.  
  537. def addIndicatorSar(self, sarDatas):
  538. """增加sar指标"""
  539. if len(sarDatas) == 0:
  540. return
  541. for sar in self.sars:
  542. self.pwKL.removeItem(sar)
  543. # 画信号
  544. for i in range(len(sarDatas)):
  545. # arrow = pg.ArrowItem(pos=(i, sarDatas[i]),headLen=10, angle=90, brush=(0, 0, 255))
  546. arrow = pg.ArrowItem(pos=(i, sarDatas[i]), angle=90, tipAngle=5, headLen=2, tailWidth=4, pen={'width': 1},
  547. brush=(0, 0, 255))
  548.  
  549. defaultOpts = {
  550. 'pxMode': True,
  551. 'angle': -150, ## If the angle is 0, the arrow points left
  552. 'pos': (0, 0),
  553. 'headLen': 10,
  554. 'tipAngle': 10,
  555. 'baseAngle': 0,
  556. 'tailLen': None,
  557. 'tailWidth': 3,
  558. 'pen': (200, 200, 200),
  559. 'brush': (255, 0, 0),
  560. }
  561. # arrow.setStyle(defaultOpts)
  562.  
  563. self.pwKL.addItem(arrow)
  564. self.sars.append(arrow)
  565.  
  566. # ----------------------------------------------------------------------
  567. def addSig(self, sig):
  568. """新增信号图"""
  569. if sig in self.sigPlots:
  570. self.pwKL.removeItem(self.sigPlots[sig])
  571. self.sigPlots[sig] = self.pwKL.plot()
  572. self.sigColor[sig] = self.allColor[0]
  573. self.allColor.append(self.allColor.popleft())
  574.  
  575. # ----------------------------------------------------------------------
  576. def showSig(self, datas):
  577. """刷新信号图"""
  578. for sig in self.sigPlots:
  579. self.sigData[sig] = datas[sig]
  580.  
  581. [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
  582. for sig in self.sigPlots] # if sig in datas]
  583.  
  584. # ----------------------------------------------------------------------
  585. def plotMark(self):
  586. """显示开平仓信号"""
  587. # 检查是否有数据
  588. if len(self.datas) == 0:
  589. return
  590. for arrow in self.arrows:
  591. self.pwKL.removeItem(arrow)
  592. # 画买卖信号
  593. for i in range(len(self.listSig)):
  594. # 无信号
  595. if self.listSig[i] == None:
  596. continue
  597. # 买信号
  598. elif self.listSig[i] != None:
  599. direction = self.listSig[i]["direction"]
  600. offset = self.listSig[i]["offset"]
  601. price = self.listSig[i]["price"]
  602.  
  603. if direction == "空" and offset == "开仓":
  604. # arrow = pg.ArrowItem(pos=(i, price), angle=-90, brush=(255, 0, 0))
  605. arrow = pg.ArrowItem(pos=(i, price), angle=180, tipAngle=60, headLen=8, tailLen=3, tailWidth=5,
  606. pen={'color': 'w', 'width': 1}, brush='r')
  607. elif direction == "多" and offset == "开仓":
  608. # arrow = pg.ArrowItem(pos=(i, price), angle=90, brush=(255, 0, 0))
  609. arrow = pg.ArrowItem(pos=(i, price), angle=180, tipAngle=60, headLen=8, tailLen=3, tailWidth=5,
  610. pen={'color': 'w', 'width': 1}, brush='b')
  611. elif direction == "空" and offset == "平仓":
  612. # arrow = pg.ArrowItem(pos=(i, price), angle=-90, brush=(0, 0, 255))
  613. arrow = pg.ArrowItem(pos=(i, price), angle=0, tipAngle=40, headLen=8, tailLen=None, tailWidth=8,
  614. pen={'color': 'w', 'width': 1}, brush='y')
  615. elif direction == "多" and offset == "平仓":
  616. # arrow = pg.ArrowItem(pos=(i, price), angle=90, brush=(0, 0, 255))
  617. arrow = pg.ArrowItem(pos=(i, price), angle=0, tipAngle=40, headLen=8, tailLen=None, tailWidth=8,
  618. pen={'color': 'w', 'width': 1}, brush='y')
  619. self.pwKL.addItem(arrow)
  620. self.arrows.append(arrow)
  621.  
  622. # ----------------------------------------------------------------------
  623. def updateAll(self):
  624. """
  625. 手动更新所有K线图形,K线播放模式下需要
  626. """
  627. datas = self.datas
  628. self.volume.update()
  629. self.candle.update()
  630.  
  631. def update(view, low, high):
  632. vRange = view.viewRange()
  633. xmin = max(0, int(vRange[0][0]))
  634. xmax = max(0, int(vRange[0][1]))
  635. xmax = min(xmax, len(datas))
  636. if len(datas) > 0 and xmax > xmin:
  637. ymin = min(datas[xmin:xmax][low])
  638. ymax = max(datas[xmin:xmax][high])
  639. if ymin and ymax:
  640. view.setRange(yRange=(ymin, ymax))
  641. else:
  642. view.setRange(yRange=(0, 1))
  643.  
  644. update(self.pwKL.getViewBox(), 'low', 'high')
  645. update(self.pwVol.getViewBox(), 'volume', 'volume')
  646. #update(self.pwOI.getViewBox(), 'openInterest', 'openInterest')
  647. update(self.curveOI.getViewBox(), 'openInterest', 'openInterest')
  648.  
  649. # ----------------------------------------------------------------------
  650. def plotAll(self, redraw=True, xMin=0, xMax=-1):
  651. """
  652. 重画所有界面
  653. redraw :False=重画最后一根K线; True=重画所有
  654. xMin,xMax : 数据范围
  655. """
  656. # xMax = len(self.datas) if xMax < 0 else xMax
  657. # self.countK = xMax-xMin
  658. # self.index = int((xMax+xMin)/2)
  659. if redraw:
  660. xmax = len(self.datas) if xMax < 0 else xMax
  661. xmin=max(0,xmax-self.countK)
  662. self.index = int((xmax + xmin) / 2)
  663. self.pwOI.setLimits(xMin=xMin, xMax=xMax)
  664. self.pwKL.setLimits(xMin=xMin, xMax=xMax)
  665. self.pwVol.setLimits(xMin=xMin, xMax=xMax)
  666. self.plotKline(redraw, xMin, xMax) # K线图
  667. self.plotVol(redraw, xMin, xMax) # K线副图,成交量
  668. self.plotOI(0, len(self.datas)) # K线副图,持仓量
  669. self.refresh()
  670.  
  671. def refreshHeight(self):
  672. # super.__init__(QResizeEvent)
  673. # 如果窗口最大化,不修改比例,防御性设计
  674. # if self.isMaximized():
  675. # return
  676. if len(self.datas)!=0:
  677. height =self.rect().height()
  678. if height!=self.oldsize:
  679. self.oldsize=height
  680. height=(height-30)/4
  681.  
  682. self.pwKL.setMinimumHeight(height * 2-24)
  683. self.pwVol.setMaximumHeight(height)
  684. self.pwVol.setMinimumHeight(12)
  685. self.pwOI.setMaximumHeight(height)
  686. self.pwOI.setMinimumHeight(12)
  687.  
  688. #print height
  689.  
  690. # ----------------------------------------------------------------------
  691. def refresh(self):
  692. """
  693. 刷新三个子图的现实范围
  694. """
  695. datas = self.datas
  696. minutes = int(self.countK / 2)
  697. xmin = max(0, self.index - minutes)
  698. xmax = xmin + 2 * minutes
  699. self.pwOI.setRange(xRange=(xmin, xmax))
  700. self.pwKL.setRange(xRange=(xmin, xmax))
  701. self.pwVol.setRange(xRange=(xmin, xmax))
  702.  
  703. # ----------------------------------------------------------------------
  704. # 快捷键相关
  705. # ----------------------------------------------------------------------
  706. def onNxt(self):
  707. """跳转到下一个开平仓点"""
  708. if len(self.listSig) > 0 and not self.index is None:
  709. datalen = len(self.listSig)
  710. self.index += 1
  711. while self.index < datalen and self.listSig[self.index] == 0:
  712. self.index += 1
  713. self.refresh()
  714. x = self.index
  715. y = self.datas[x]['close']
  716. self.crosshair.signal.emit((x, y))
  717.  
  718. # ----------------------------------------------------------------------
  719. def onPre(self):
  720. """跳转到上一个开平仓点"""
  721. if len(self.listSig) > 0 and not self.index is None:
  722. self.index -= 1
  723. while self.index > 0 and self.listSig[self.index] == 0:
  724. self.index -= 1
  725. self.refresh()
  726. x = self.index
  727. y = self.datas[x]['close']
  728. self.crosshair.signal.emit((x, y))
  729.  
  730. # ----------------------------------------------------------------------
  731. def onDown(self):
  732. """放大显示区间"""
  733. self.countK = min(len(self.datas), int(self.countK * 1.2) + 1)
  734. self.refresh()
  735. if len(self.datas) > 0:
  736. x = self.index - self.countK / 2 + 2 if int(
  737. self.crosshair.xAxis) < self.index - self.countK / 2 + 2 else int(self.crosshair.xAxis)
  738. x = self.index + self.countK / 2 - 2 if x > self.index + self.countK / 2 - 2 else x
  739. x=min(x,len(self.datas)-1)
  740. y = self.datas[x][2]
  741. # y = self.datas[x]['close']
  742. self.crosshair.signal.emit((x, y))
  743.  
  744. # ----------------------------------------------------------------------
  745. def onUp(self):
  746. """缩小显示区间"""
  747. # self.countK = max(3,int(self.countK/1.2)-1)
  748. self.countK = max(20, int(self.countK / 1.2) - 1) # 最小显示k线范围20
  749. self.refresh()
  750. if len(self.datas) > 0:
  751. x = self.index - self.countK / 2 + 2 if int(
  752. self.crosshair.xAxis) < self.index - self.countK / 2 + 2 else int(self.crosshair.xAxis)
  753. x = self.index + self.countK / 2 - 2 if x > self.index + self.countK / 2 - 2 else x
  754. x = min(x, len(self.datas)-1)
  755. y = self.datas[x]['close']
  756. self.crosshair.signal.emit((x, y))
  757.  
  758. # ----------------------------------------------------------------------
  759. def onLeft(self):
  760. """向左移动"""
  761. if len(self.datas) > 0 and int(self.crosshair.xAxis) > 2:
  762. x = int(self.crosshair.xAxis) - 1
  763. y = self.datas[x]['close']
  764. if x <= self.index - self.countK / 2 + 2 and self.index > 1:
  765. self.index -= 1
  766. self.refresh()
  767. self.crosshair.signal.emit((x, y))
  768.  
  769. # ----------------------------------------------------------------------
  770. def onRight(self):
  771. """向右移动"""
  772. if len(self.datas) > 0 and int(self.crosshair.xAxis) < len(self.datas) - 1:
  773. x = int(self.crosshair.xAxis) + 1
  774. y = self.datas[x]['close']
  775. if x >= self.index + int(self.countK / 2) - 2:
  776. self.index += 1
  777. self.refresh()
  778. self.crosshair.signal.emit((x, y))
  779.  
  780. # ----------------------------------------------------------------------
  781. # 界面回调相关
  782. # ----------------------------------------------------------------------
  783. def onPaint(self):
  784. """界面刷新回调"""
  785. view = self.pwKL.getViewBox()
  786. vRange = view.viewRange()
  787. # xmin = max(0,int(vRange[0][0]))
  788. # xmax = max(0,int(vRange[0][1]))
  789. # self.index = int((xmin+xmax)/2)+1
  790.  
  791. # ----------------------------------------------------------------------
  792. def resignData(self, datas):
  793. """更新数据,用于Y坐标自适应"""
  794. self.crosshair.datas = datas
  795.  
  796. def viewXRangeChanged(low, high, self):
  797. vRange = self.viewRange()
  798. xmin = max(0, int(vRange[0][0]))
  799. xmax = max(0, int(vRange[0][1]))
  800. xmax = min(xmax, len(datas))
  801. if len(datas) > 0 and xmax > xmin:
  802. ymin = min(datas[xmin:xmax][low])
  803. ymax = max(datas[xmin:xmax][high])
  804. if ymin and ymax:
  805. self.setRange(yRange=(ymin, ymax))
  806. else:
  807. self.setRange(yRange=(0, 1))
  808.  
  809. view = self.pwKL.getViewBox()
  810. view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'low', 'high'))
  811. view = self.pwVol.getViewBox()
  812. view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))
  813.  
  814. view = self.pwOI.getViewBox()
  815. view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))
  816.  
  817. # ----------------------------------------------------------------------
  818. # 数据相关
  819. # ----------------------------------------------------------------------
  820. def clearData(self):
  821. """清空数据"""
  822. # 清空数据,重新画图
  823. self.time_index = []
  824. self.listBar = []
  825. self.listVol = []
  826. self.listLow = []
  827. self.listHigh = []
  828. self.listOpenInterest = []
  829. self.listSig = []
  830. self.sigData = {}
  831. self.arrows = []
  832. self.datas = None
  833.  
  834. # ----------------------------------------------------------------------
  835. def updateSig(self, sig):
  836. """刷新买卖信号"""
  837. self.listSig = sig
  838. self.plotMark()
  839. # ----------------------------------------------------------------------
  840. def onBar(self, bar, nWindow=20):
  841. """
  842. 新增K线数据,K线播放模式
  843. nWindow : 最大数据窗口
  844. """
  845. # 是否需要更新K线
  846. newBar = False if len(self.datas) > 0 and bar.datetime == self.datas[-1].datetime else True
  847. nrecords = len(self.datas) if newBar else len(self.datas) - 1
  848. bar.openInterest = np.random.randint(0,
  849. 3) if bar.openInterest == np.inf or bar.openInterest == -np.inf else bar.openInterest
  850. recordVol = (nrecords, bar.volume, 0, 0, bar.volume) if bar.close < bar.open else (
  851. nrecords, 0, bar.volume, 0, bar.volume)
  852. if newBar and any(self.datas):
  853. self.datas.resize(nrecords + 1, refcheck=0)
  854. self.listBar.resize(nrecords + 1, refcheck=0)
  855. self.listVol.resize(nrecords + 1, refcheck=0)
  856. elif any(self.datas):
  857. self.listLow.pop()
  858. self.listHigh.pop()
  859. self.listOpenInterest.pop()
  860. if any(self.datas):
  861. self.datas[-1] = (bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)
  862. self.listBar[-1] = (nrecords, bar.open, bar.close, bar.low, bar.high)
  863. self.listVol[-1] = recordVol
  864. else:
  865. self.datas = np.rec.array(
  866. [(datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)], \
  867. names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
  868. self.listBar = np.rec.array([(nrecords, bar.open, bar.close, bar.low, bar.high)], \
  869. names=('datetime', 'open', 'close', 'low', 'high'))
  870. self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
  871. self.resignData(self.datas)
  872. self.axisTime.update_xdict({nrecords: bar.datetime})
  873. self.listLow.append(bar.low)
  874. self.listHigh.append(bar.high)
  875. self.listOpenInterest.append(bar.openInterest)
  876. # if newBar:
  877. # xMax = nrecords + 1
  878. # xMin = max(0, xMax - self.countK) # 最小显示区间
  879. # self.index=int(xMax+xMin)/2
  880.  
  881. # if self.pwKL.getViewBox().mousePress == True:
  882. # print("sssssssssss")
  883.  
  884. # if self.pwKL.getViewBox().mousePress == False: ##self.mousePress==false,不在执行刷新操作
  885.  
  886. xMax = self.index + self.countK
  887. xMin = self.index - self.countK
  888. #xMin=(0,xMin)
  889. if xMin<0:
  890. xMin=0
  891.  
  892. if not newBar:
  893. self.updateAll()
  894.  
  895. self.plotAll(False, xMin, xMax)
  896. self.crosshair.signal.emit((None, None))
  897.  
  898. # ----------------------------------------------------------------------
  899. def loadData(self, datas):
  900. """
  901. 载入pandas.DataFrame数据
  902. datas : 数据格式,cols : datetime, open, close, low, high
  903. """
  904. # 设置中心点时间
  905. self.index = 0
  906.  
  907. # 绑定数据,更新横坐标映射,更新Y轴自适应函数,更新十字光标映射
  908. datas['time_int'] = np.array(range(len(datas.index)))
  909. self.datas = datas[['open', 'close', 'low', 'high', 'volume', 'openInterest']].to_records()
  910. self.axisTime.xdict = {}
  911. xdict = dict(enumerate(datas.index.tolist()))
  912. self.axisTime.update_xdict(xdict)
  913. self.resignData(self.datas)
  914. # 更新画图用到的数据
  915. self.listBar = datas[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
  916. self.listHigh = list(datas['high'])
  917. self.listLow = list(datas['low'])
  918. self.listOpenInterest = list(datas['openInterest'])
  919. # 成交量颜色和涨跌同步,K线方向由涨跌决定
  920. datas0 = pd.DataFrame()
  921. datas0['open'] = datas.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
  922. datas0['close'] = datas.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
  923. datas0['low'] = datas0['open']
  924. datas0['high'] = datas0['close']
  925. datas0['time_int'] = np.array(range(len(datas.index)))
  926. self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
  927. # 调用画图函数
  928. self.plotAll(True, 0, len(self.datas))
  929.  
  930. def loadDataBar(self, datas):
  931. """
  932. 载入pandas.DataFrame数据
  933. datas : 数据格式,'symbol','vtSymbol','exchange','open','high','low','close','date','time1','datetime','volume','openInterest'
  934. """
  935. print(datas)
  936. self.index = 0
  937.  
  938. # 绑定数据,更新横坐标映射,更新Y轴自适应函数,更新十字光标映射
  939. datas['time_int'] = np.array(range(len(datas.index)))
  940.  
  941. self.datas = datas[['datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest']].to_records(False)
  942. # self.datas = datas[['symbol','vtSymbol','exchange','open','high','low','close','date','time1','datetime','volume','openInterest']].to_records()
  943. self.axisTime.xdict = {}
  944. # xdict = dict(enumerate(datas.index.tolist()))
  945. xdict = dict(enumerate(datas['datetime'].tolist()))
  946. self.axisTime.update_xdict(xdict)
  947. self.resignData(self.datas)
  948. # 更新画图用到的数据
  949. self.listBar = datas[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
  950. self.listHigh = list(datas['high'])
  951. self.listLow = list(datas['low'])
  952. self.listOpenInterest = list(datas['openInterest'])
  953. # 成交量颜色和涨跌同步,K线方向由涨跌决定
  954. datas0 = pd.DataFrame()
  955. datas0['open'] = datas.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
  956. datas0['close'] = datas.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
  957. datas0['low'] = datas0['open']
  958. datas0['high'] = datas0['close']
  959. datas0['time_int'] = np.array(range(len(datas.index)))
  960. self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
  961. # 调用画图函数
  962. self.plotAll(True, 0, len(self.datas))
  963.  
  964. ##VtBarData 结构
  965. def loadDataBarArray(self, record):
  966. txtData = pd.DataFrame.from_records(record, index="datetime")
  967. # 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"})
  968.  
  969. self.loadData(txtData)
  970.  
  971. ########################################################################
  972. # 功能测试
  973. ########################################################################
  974. import sys
  975.  
  976. if __name__ == '__main__':
  977. app = QApplication(sys.argv)
  978. # 界面设置
  979. cfgfile = QtCore.QFile('css.qss')
  980. cfgfile.open(QtCore.QFile.ReadOnly)
  981. styleSheet = cfgfile.readAll()
  982. styleSheet = unicode(styleSheet, encoding='utf8')
  983. app.setStyleSheet(styleSheet)
  984. # K线界面
  985. # ui = KLineWidget()
  986. # ui.show()
  987. # ui.KLtitle.setText('rb1701',size='20pt')
  988. # ui.loadData(pd.DataFrame.from_csv('data.csv'))
  989.  
  990. ui = KLineWidget(name="opt")
  991. ui.show()
  992. ui.KLtitle.setText('rb1701', size='20pt')
  993.  
  994. # txtData = pd.DataFrame.from_csv('D:/data/day/rb1101.txt',header=None,index_col=7)
  995. txtData = pd.DataFrame.from_csv('E:/Data/day/rb1101.txt', header=None, index_col=7)
  996.  
  997. txtData = txtData.rename(
  998. columns={0: 'symbol', 1: "vtSymbol", 2: "exchange", 3: "open", 4: "high", 5: "low", 6: "close", 7: "date",
  999. 8: "time", 9: "datetime", 10: "volume", 11: "openInterest"})
  1000.  
  1001. ui.loadDataBar(txtData)
  1002. app.exec_()

python kline的更多相关文章

  1. 小白学Python(14)——pyecharts 绘制K线图 Kline/Candlestick

    Kline-基本示例 from pyecharts import options as opts from pyecharts.charts import Kline data = [ [2320.2 ...

  2. Python:数据可视化pyecharts的使用

    什么是pyecharts? pyecharts 是一个用于生成 Echarts 图表的类库. echarts 是百度开源的一个数据可视化 JS 库,主要用于数据可视化.pyecharts 是一个用于生 ...

  3. Python交互K线工具 K线核心功能+指标切换

    Python交互K线工具 K线核心功能+指标切换 aiqtt团队量化研究,用vn.py回测和研究策略.基于vnpy开源代码,刚开始接触pyqt,开发界面还是很痛苦,找了很多案例参考,但并不能完全满足我 ...

  4. Python数据可视化系列-02-pyecharts可视化非常cool

    pyecharts介绍 pyecharts网站 Pyecharts生成的图像,动态效果非常cool.在HTML上展示很是perfect.matplotlib用于科研,但是pyecharts用于展示和讲 ...

  5. 【python可视化系列】python数据可视化利器--pyecharts

    学可视化就跟学弹吉他一样,刚开始你会觉得自己弹出来的是噪音,也就有了在使用python可视化的时候,总说,我擦,为啥别人画的图那么溜: [python可视化系列]python数据可视化利器--pyec ...

  6. 量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)(转)

    量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python) 原文地址:http://blog.csdn.net/u012234115/article/details/728300 ...

  7. 小白学Python(8)——pyecharts 入门

    简介: pyecharts 是一个用于生成 Echarts 图表的类库. echarts 是百度开源的一个数据可视化 JS 库,主要用于数据可视化.pyecharts 是一个用于生成 Echarts ...

  8. Python爬取上交所一年大盘数据

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 半个码农2018 PS:如有需要Python学习资料的小伙伴可以加点 ...

  9. Python股票历史数据的获取

    获取股票数据的接口很多,免费的接口有新浪.网易.雅虎的API接口,收费的就是证券公司及相应的公司提供的接口.收费试用的接口一般提供的数据只是最近一年或三年的,限制比较多,除非money足够多.所以本文 ...

随机推荐

  1. Failed to set MokListRT: Invalid Parameter Something as gone seriously wrong: import_mok_state() failed: Invalid Parameter

    今天yum update升级centos7,重启后发现开不了机,报错如下: Failed to set MokListRT: Invalid ParameterSomething as gone se ...

  2. debug apk logCat

    Microsoft Windows [版本 10.0.15063](c) 2017 Microsoft Corporation.保留所有权利. C:\Users\Administrator>ad ...

  3. MyEclipse破解步骤

    1.下载安装(注:安装后不要打开myeclipse,以下步骤完成方可打开) 2.解压破解文件 3.打开文件夹patch 3.全部复制 4.找到myeclipse的安装目录,打开plugins文件夹 5 ...

  4. rinetd 通过公网连接云数据库

    在很多云服务中,经常会遇到云存储数据库没有公网(外网)地址,只有内网地址,这导致在公司网无法访问,这是一个很困扰的问题,这时我们可以使用rinetd进行转发实现外网连接. 首先需要一台能够连接上数据库 ...

  5. http协议中的响应代码从 1xx ~ 5xx,一共有41种

    http协议中的响应代码从 1xx ~ 5xx,一共有41种 http://how2j.cn/k/http/http-response-code/572.html

  6. 基于zigbee协议的空中下载技术(OTA)

    首先镜像服务器的解释: 镜像服务器(Mirror server)与主服务器的服务内容都是一样的,只是放在一个不同的地方,分担主机的负载. 简单来说就是和照镜子似的,能看,但不是原版的.在网上内容完全相 ...

  7. 在sql中select的执行顺序

    <select{[distinct |all] columns |*}> [into table_name] <from {tables |views | other select} ...

  8. 使用OGG添加唯一标识字段到目标表

    利用GoldenGate,可以获取到变更记录在源端对应的redo日志序号,redo中的地址RBA,如果源端是RAC,还可以拿到源端节点的编号,通过这3个值,可以定位该变更记录的唯一性. 这些信息,在G ...

  9. callable函数 stride的意义 Math.round(),Math.ceil(),Math.floor()用法

    callable()函数检查一个函数是否可以调用 如果返回True,object仍然可能调用失败:但如果返回False,调用对象ojbect绝对不会成功. 对于函数, 方法, lambda 函式, 类 ...

  10. 爬微信好友签名和QQ好友签名

    先说如何爬微信好友签名,主要使用itchat,这个库提供直接的api来获取好友信息,只要用正则过滤出就行了.说一下步骤,就不贴代码了.# 登陆# 获取好友列表# 提取签名# jieba分词# word ...