5、总结
尽管Qt库自己使用C++开发,相比之下在Python中使用PyQt构建GUI程序更为快捷、简便。基于Python动态语言的特性,在不少不是特别追求性能(GUI更新速度)的地方能够大幅减小用户的代码编写量,而且下降出错率。
请记住vn.py从开始就是一款专门为交易员设计的通用型交易平台开发框架(而不止是全自动的程序化交易),在金融市场上真正能帮助交易员赚钱的绝对不是多么复杂的程序算法,而是可以完美实现交易员的交易策略而且越简单越好的工具。
【前言】整合底层接口的各项功能到中层引擎中后,当咱们开发顶层应用时(GUI或者策略算法)。只需知道中层引擎对外提供的主动API函数以及事件引擎中相关的事件类型和数据形式便可。html
在GUI和策略算法这两个主要类型的顶层应用中,先介绍GUI开发的缘由是:目前国内支持用户定制化开发GUI界面的量化平台少之又少,而包含一个比较全面的GUI开发教程的则据我所知尚未。随着国内愈来愈多的衍生品推出(期权、分级基金、将来的反向基金),不少新型的交易策略从全自动转向了半自动,常常须要交易员的手动干预(启动暂停策略、盘中微调参数等),以及投资组合层面的风险管理(期权希腊值、分级基金行业暴露等),这种状况下传统上仅支持策略算法开发的量化交易平台变得愈加难以知足交易员的需求(包括做者本人),因此估计这方面的文章更能填补当前市场需求的空缺(笑...)。算法
我司的交易平台是没有gui的,因此我能够基于此开发GUI的监控系统。api
参考于系列文章:http://www.vnpy.org/basic-tutorial-8.html服务器
目前Python上主要的GUI开发工具包括:tkinter、PyQt、PyGTK和wxPython,笔者选择PyQt的主要缘由是:多线程
Anaconda中已经包含(早期版本中包含的是LGPL协议的Pyside,稳定性不如PyQt)框架
另外一个内置GUI库tkinter的功能太弱ide
网上对这四款GUI开发工具比较分析的文章不少,有兴趣的读者能够本身搜搜看。函数
接下来的几篇GUI开发教程会假设读者已经对PyQt开发有了必定的了解,主要针对和量化交易平台开发相关的部分,须要补充基础知识的读者建议参考如下资源:工具
zetcode.com,上面的教程简单明了,从头至尾作一遍对PyQt的工做原理基本就有个全面的了解了性能
Rapid GUI Programming with Python and Qt,一本由Riverbank(PyQt的开发公司)员工推出的PyQt开发教程,很是细致全面,可是部份内容跳跃性太大,须要从头至尾多看几遍
也能够在遇到特定问题时搜百度或者StackOverflow,一般都能找到答案
PyQt版本 在Riverbank官网下载:
PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe Windows 32 bit installer
请特别注意这个版本的问题,从最近几个月的经验看,不少PyQt相关模块运行时报错就是由于下错了版本(PyQt五、64位等等都不对)。
从功能上看,全部交易平台的GUI组件均可以分为两类,数据监控(被动)和功能调用(主动),固然也有同时混合两类功能的组件。
行情监控组件,用于监控实时行情数据,每当API端有新的行情数据推送时当即进行更新。
二、功能调用
帐号登陆组件,用于调用中层引擎的登陆功能,传入用户名、密码、服务器地址等参数。
三、 混合(交易下单)
交易下单组件,左侧部分用于填入下单参数后调用中层引擎的下单功能发单,右侧部分用于监控用户输入的合约代码的实时行情(有行情推送时当即更新)。
数据监控组件主要用于对交易平台中的各项数据实现实时更新或者手动更新的显示监控,最经常使用的包括行情、报单、成交、持仓和日志等。监控组件中最多见的类型是表格,对应PyQt中的QTableWidget组件,表格中的单元格则使用QTableWidgetItem组件。对于某些特殊的数据监控也可使用其余类型的组件,如上面交易下单组件中右侧的部分,主要是使用标签组件QLabel构成的。
针对不一样的监控内容须要实现不一样的数据更新方法,例如日志、成交类数据应该使用插入更新(即每条新的数据都应该插入新的一行),行情数据应该使用固定位置更新(即在表格中固定的单元格位置更新数据),以及主要针对持仓和报单数据的混合更新(即已经存在的数据直接在对应的位置更新,不然插入新的一行)。
下面以最基础的日志监控为例介绍监控组件的实现原理,其余更为复杂的监控组件建议用户直接阅读vn.demo中demoMain.py的源代码,大部分代码做者都作了详尽的注释。
日志监控组件主要用于输出程序运行过程当中有关当前程序运行状态的信息。
整个实现代码以下:
######################################################################## class LogMonitor(QtGui.QTableWidget): """用于显示日志""" signal = QtCore.pyqtSignal(type(Event())) #---------------------------------------------------------------------- def __init__(self, eventEngine, parent=None): """Constructor""" super(LogMonitor, self).__init__(parent) self.__eventEngine = eventEngine self.initUi() self.registerEvent() #---------------------------------------------------------------------- def initUi(self): """初始化界面""" self.setWindowTitle(u'日志') self.setColumnCount(2) self.setHorizontalHeaderLabels([u'时间', u'日志']) self.verticalHeader().setVisible(False) # 关闭左边的垂直表头 self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态 # 自动调整列宽 self.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) self.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) #---------------------------------------------------------------------- def registerEvent(self): """注册事件监听""" # Qt图形组件的GUI更新必须使用Signal/Slot机制,不然有可能致使程序崩溃 # 所以这里先将图形更新函数做为Slot,和信号链接起来 # 而后将信号的触发函数注册到事件驱动引擎中 self.signal.connect(self.updateLog) self.__eventEngine.register(EVENT_LOG, self.signal.emit) #---------------------------------------------------------------------- def updateLog(self, event): """更新日志""" # 获取当前时间和日志内容 t = time.strftime('%H:%M:%S',time.localtime(time.time())) log = event.dict_['log'] # 在表格最上方插入一行 self.insertRow(0) # 建立单元格 cellTime = QtGui.QTableWidgetItem(t) cellLog = QtGui.QTableWidgetItem(log) # 将单元格插入表格 self.setItem(0, 0, cellTime) self.setItem(0, 1, cellLog)
接下来逐段讲解:
一、对象初始化(init)
1 #---------------------------------------------------------------------- 2 def __init__(self, eventEngine, parent=None): 3 """Constructor""" 4 super(LogMonitor, self).__init__(parent) 5 self.__eventEngine = eventEngine 6 7 self.initUi() 8 self.registerEvent()
建立对象时,咱们须要传入程序中的事件驱动引擎对象eventEngine,以及该图形组件所依附的母组件对象parent(通常能够留空)
把eventEngine对象的引用保存到__eventEngine上后,咱们调用initUi方法初始化图形组件的界面,以及registerEvent方法来向事件引擎中注册该图形组件的事件监听函数。
二、初始化界面(initUi)
1 #---------------------------------------------------------------------- 2 def initUi(self): 3 """初始化界面""" 4 self.setWindowTitle(u'日志') 5 6 self.setColumnCount(2) 7 self.setHorizontalHeaderLabels([u'时间', u'日志']) 8 9 self.verticalHeader().setVisible(False) # 关闭左边的垂直表头 10 self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态 11 12 # 自动调整列宽 13 self.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) 14 self.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch)
首先设置该图形组件左上方的标题栏内容为“日志”(日志监控组件)
咱们但愿显示日志时,每行显示该条日志的生成时间和具体的日志内容
因为QTableWidget自己比较相似于Excel表格,左侧有垂直标题栏(默认用于显示每行行号的表头)且能够编辑,咱们须要关闭这两个功能
另外咱们但愿显示日志生成时间的列的列宽能够调整为最小(只要能看见完整的时间就行),而把显示日志内容的列设为拉升(即窗口有多宽都彻底覆盖)
三、注册事件监听(registerEvent)
这里须要稍微深刻一下vn.py框架中的多线程工做机制:
整个框架在Python环境中主要包含两个线程:主线程(运行Qt循环)和事件处理线程(运行EventEngine中的工做循环)
针对用户是否须要使用GUI界面,主线程中运行的Qt循环能够选择QApplication(带GUI)或者QCoreApplication(纯cmd)
Qt循环主要负责处理全部GUI相关的操做(控件绘制、信号处理等等),用户不能在其余线程中直接改变GUI界面上的任何内容,不然可能会直接致使程序崩溃
当用户但愿在其余线程中对GUI进行操做时,必须依赖Qt提供的signal/slot机制,Qt循环的底层也运行着一个相似于EventEngine的事件处理机制,其余线程发出signal后会首先记录到一个队列中,而后由Qt对队列中的signal任务进行循环处理(具体请参考Qt相关的资料)
事件处理线程的工做原理在以前的教程中已经专门介绍过了,这里再也不重复,用户只需记住全部Qt GUI组件的事件处理函数,都必须使用一个signal和该函数相连,而且在向事件引擎中注册函数监听时,将该signal的emit方法代替本来的事件处理函数进行注册
######################################################################## class LogMonitor(QtGui.QTableWidget): """用于显示日志""" signal = QtCore.pyqtSignal(type(Event()))
六、signal的建立须要放在类的构造中,而不能放在类的初始化函数里(Qt会直接报错)
七、因为事件驱动引擎在调用监听函数时会传入事件对象自己做为参数,所以在建立signal时须要容许传入一个类型为Event的参数
#---------------------------------------------------------------------- def registerEvent(self): """注册事件监听""" # Qt图形组件的GUI更新必须使用Signal/Slot机制,不然有可能致使程序崩溃 # 所以这里先将图形更新函数做为Slot,和信号链接起来 # 而后将信号的触发函数注册到事件驱动引擎中 self.signal.connect(self.updateLog) self.__eventEngine.register(EVENT_LOG, self.signal.emit)
八、首先咱们把signal和事件处理函数updateLog链接.
九、而后将signal的emit方法注册到事件驱动引擎中,监听EVENT_LOG类型的事件
四、更新日志记录(updateLog)
#---------------------------------------------------------------------- def updateLog(self, event): """更新日志""" # 获取当前时间和日志内容 t = time.strftime('%H:%M:%S',time.localtime(time.time())) log = event.dict_['log'] # 在表格最上方插入一行 self.insertRow(0) # 建立单元格 cellTime = QtGui.QTableWidgetItem(t) cellLog = QtGui.QTableWidgetItem(log) # 将单元格插入表格 self.setItem(0, 0, cellTime) self.setItem(0, 1, cellLog)
尽管Qt库自己使用C++开发,相比之下在Python中使用PyQt构建GUI程序更为快捷、简便。基于Python动态语言的特性,在不少不是特别追求性能(GUI更新速度)的地方能够大幅减小用户的代码编写量,而且下降出错率。
请记住vn.py从开始就是一款专门为交易员设计的通用型交易平台开发框架(而不止是全自动的程序化交易),在金融市场上真正能帮助交易员赚钱的绝对不是多么复杂的程序算法,而是可以完美实现交易员的交易策略而且越简单越好的工具。