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從開始就是一款專門爲交易員設計的通用型交易平臺開發框架(而不止是全自動的程序化交易),在金融市場上真正能幫助交易員賺錢的絕對不是多麼複雜的程序算法,而是可以完美實現交易員的交易策略而且越簡單越好的工具。