我们在第16章用浏览器实现过绘制技术指标,但当技术指标数量多时浏览器容易卡顿,所以我们需要一种更高性能的绘图方法。
Python绘图库最著名的莫属matplotlib,matplotlib强大、专业,适用于各种数据分析领域,但在实时绘制K线图时性能有些低,尤其是数据量大的时候更显不足,我们推荐使用PyQtGraph,PyQtGraph提供了高度优化的绘图接口。
本章借鉴了开源量化平台VNPY绘制K线的方法。
PyQtGraph依赖于PyQt,可与PyQt配合使用,PyQtGraph可嵌入PyQt的部件组成丰富的GUI界面程序。
安装pyqtgraph:
20.1、PyQtGraph简介:
PyQtGraph应用基于项的图形概念,例如,窗口中的一个图片就是一个项,图片上的一个曲线或一个文字也是一个项,窗口由GraphicsView或其子类创建,图形项由QGraphicsItem或其子类创建,窗口包括窗口中的各种图形项组成一个图形视图。
图形视图需要创建一个场景(scene),类似舞台表演之前需要建立一个环境用来搭建舞台,这个场景是通过QGraphicsScene对象实现的,通常会自动创建。
场景用来管理图形项,对场景中的一个项变换,也会应用到其子对象,例如移动一个图片,图片上的各类图形会跟着移动。
图形视图使用三种坐标系,最顶层的窗口使用物理坐标系,类似画图时的画框,物理坐标系左上角坐标原点(0,0),场景使用逻辑坐标系,类似画框上的画图区,第三种坐标系是图形项使用的坐标系,它是以(0,0)为原点的逻辑坐标系,每个项的原点就是该项在场景中的位置。
图形项也称为图元Item,图元Item通过创建QGraphicsItem的子类来创建,在子类中需要实现它的两个纯虚拟公共函数:boundingRect(),它返回图元绘制的区域的估计值,即图元在这个函数返回的范围内绘制;paint(),它实现实际的绘画,绘制基于逻辑坐标系,默认原点是(0,0)。这两个函数会被自动调用。
图形视图的层次结构如下:
图元Item有以下三种:
数据类(QGraphicsItem的子类)
容器类(QGraphicsItem的子类;包含其他QGraphicsItem对象,必须在GraphicsView中查看)
容器类(QWidget的子类;可以嵌入在PyQtGUI中)
在PyQtGraph中,有以下几种绘制数据类图形的方法:
plot()方法的基本参数如下:
PyQtGraph提供了两种类似的方法来进行全局设置:
我们可以这么使用:
背景颜色为pyqtgraph.mkColor函数的合法输入,包括以下形式:
20.2、技术指标绘制:
20.2.1、K线和成交量绘制类:
首先需要记录K线,函数get_kline_serial()订阅K线后K线的数量便固定了,K线更新时只是修改原数据,不会增加数量,因此若想记录K线的增加可使用如下类:
初始化BarManager时传入订阅的K线,然后随着K线更新反复调用update_bar()即可把新K线追加到变量self.bars中。
若要画价差K线,也可以模仿K线记录类编写价差K线的记录类,例如:
由于不同指标和K线需要对齐x轴,以免图形发生错位,简单的处理方法是重置K线数据的行索引,用K线的总数量重置行索引,绘图时x轴会按行索引对齐。
K线包含影线和实体,影线是线段,实体是矩形,成交量以柱体显示也是矩形,我们用QPainter类来画K线和成交量,QPainter需要在一个绘图设备上画图,绘图设备包含QPixmap、QImage、QBitmap、QPicture,我们用的绘图设备是QPicture,QPicture用来记录QPainter的绘制指令,在paint()中重放QPicture记录的绘制指令即可画图。
例如,用以下函数绘制K线:
参数ix是需要绘制K线的x轴位置,bar是一根K线,首先创建QPicture,然后创建QPainter并传入参数QPicture,QPicture便开始记录QPainter的指令,QPainter.end()结束记录,然后返回QPicture,QPicture会在paint()函数中重放画图指令,例如:
boundingRect()函数用来设置绘图的范围,例如:
随着K线更新,K线的价格范围也会变化,以下函数被反复调用以返回指定x轴范围的y轴价格范围:
成交量的绘制过程和K线类似,可把K线和成交量的公共函数写在同一个类中,K线和成交量作为子类继承该类即可,例如:
完整的代码如下:
实际使用中成交量的作用不大,用其他软件看成交量即可,没必要重画。
20.2.2、技术指标计算类:
类似均线这样的曲线,我们可创建一个曲线类,用该曲线类统一计算这类曲线,例如:
技术指标计算函数采用的是模块tqsdk.ta中的函数,模块tqsdk.ta中的函数默认用K线的收盘价计算,返回值为pandas.DataFrame类型,一开始指标周期范围内的K线不够计算的,指标值会以Nan值表示,但Nan值是不确定值,无法绘图,需要对Nan值替换,一个方法是替换成第一个计算出的指标值,即绘图时把最开始的指标画成直线。
函数update_bar会被反复调用以更新指标值,由于技术指标需要与K线的x轴对齐,因此默认以全部K线计算,但当K线数量逐渐增多时会增加计算量,如果只计算最新的K线效率会更高,但对齐x轴又需要增加更多代码量,读者可自行尝试。
MACD指标既有曲线又有直线,可以分别来画,曲线用曲线类CurveItem计算,直线可类似画K线影线那样画,则需要多创建一个画MACD柱线的类,如下:
函数tickStrings会被自动调用以转换x轴刻度。
20.2.4、指标窗口类:
我们用PlotWidget创建一个指标窗口,用GraphicsLayout创建一个图形层,图形层可以以网格的形式布局图元,我们把图形层设置为PlotWidget的中心层,例如:
我们在图形层GraphicsLayout中添加图元容器PlotItem,可按行列位置添加,在图元容器PlotItem中再添加K线或指标图形,还可以添加文字,例如:
反复调用K线类的update_bar函数即可绘制K线,曲线类需要用setData()函数更新指标值。
总结一下绘图流程:
上述是简化过程,还有一些细节工作要做,比如调用K线管理类更新K线,图元容器是否显示x轴或其他轴,图元容器x轴是否联动,比如成交量指标处在副图的图元容器,和主图K线所处的图元容器x轴应该联动,这样拖动一个图形,另一个就可以跟着移动,主图的图元称为锚定图元,其他副图图元可锚定该图元的x轴以实现联动,随着K线的更新,图元容器需调整可视范围以始终显示最新K线,不同的图元容器是否显示不同的K线,等等。
我们结合窗口类的完整代码看一下:
20.2.5、图形显示:
我们把上述几个类的代码保存在文件中,例如文件名:klineschart.py,文件作为模块导入到代码中,我们还需要用到第19章里的信号线程,我们结合代码看一下:
代码一共创建了五个图元容器,按行布局,图元容器里分别添加了K线均线、成交量、K线布林线、MACD、价差K线,代码里注释的很清楚了,结合注释就可以理解指标绘制的过程了,绘制显示效果如下: