像股票這樣的金融商品百百種,加上市場的變化性與多樣的市場規則,使得這類的分析總是相當困難。但相關數據取得與蒐集的管道也相當稀少,甚至難以整合與上手。國內幾家券商提供之API服務便顯得相當地重要。
程式環境
程式語言: Python 3.6.8 x86
作業系統: Windows 10 64位元
API版本: 2.13.16 x86版本 to 2.13.28 x86版本
解除元件
想要解除元件非常簡單,我們以系統管理員權限執行Uninstall.bat檔。如下圖所示:
看到以下訊息,代表你已經成功解除元件嚕~。
什麼?你還不放心?執行平常在使用的程式碼看看就知道嚕~。
只要你能看到像上圖這樣的報錯,就非常值得開心,你已經成功跨出一大步了。很明顯,出錯並不總是壞的,我們得學會與其共處。
重新註冊新元件
接著,我們從官方網站上下載最新的元件,以目前來說就是2.13.28版本,我習慣依版本分開存放,各位也可以直接刪除舊版元件檔案並取代之,如下圖所示:
我們觀察其結構基本上沒有變,那就很好辦了,我們依照先前的註冊方式,重新進行註冊,記得選擇x86版本,如下圖所示:
註冊同樣需要系統管理員權限,不要忘記了,成功訊息如下圖所示:
不放心的話,可以使用同目錄底下的SKCOMTester.exe執行檔進行登入確認。
回到Python主程式
既然,該解除的元件已解除,並且我們也重新進行註冊了,那是不是可以跑了呢~,你可以試試看阿。
事實上,從2.13.17版本開始,就強制在進行登入時,也要註冊回報(Reply)事件,也就是公告的部分,因此,如果我們現在直接執行,會發生如下情況:
下圖是2.13.17版本的出錯:
我們可以發現錯誤訊息稍微不一樣,到底改了哪些東西呢?這倒是不那麼重要,我們知道我們必須先註冊公告。
不過,令我疑惑的倒是,官方文件提到2.13.16版本就已經需要此步驟,才能登入,但我至今天為止仍然用得好好地。
還記得先前有寫了一個初始化API的函式嗎?
def capital_api_init(api_path_):
# link api module.
comtypes.client.GetModule(api_path_)
import comtypes.gen.SKCOMLib as sk
# Login.
skC = comtypes.client.CreateObject(sk.SKCenterLib, interface=sk.ISKCenterLib)
# Domestic Quote.
skQ = comtypes.client.CreateObject(sk.SKQuoteLib, interface=sk.ISKQuoteLib)
return sk, skC, skQ
我們要進行SKReplyLib物件的添加,:
def capital_api_init(api_path_):
# link api module.
comtypes.client.GetModule(api_path_)
import comtypes.gen.SKCOMLib as sk
# Login.
skC = comtypes.client.CreateObject(sk.SKCenterLib, interface=sk.ISKCenterLib)
# Domestic Quote.
skQ = comtypes.client.CreateObject(sk.SKQuoteLib, interface=sk.ISKQuoteLib)
# Reply.
skR = comtypes.client.CreateObject(sk.SKReplyLib, interface=sk.ISKReplyLib)
return sk, skC, skQ, skR
其中,我們同樣使用comtypes.client.CreateObject()函式來建立物件,並且capital_api_init()函式會額外回傳出這個物件。
而為了方便區分不同功能,我們另外為這個事件建立一個Class,這個Class中,我們會需要有一個OnReplyMessage()的函式:
class SKReplyLibEvents:
def __init__(self):
pass
def OnReplyMessage(self, bstrUserID, bstrMessages):
sConfirmCode = -1
print('[OnReplyMessage]', bstrUserID, bstrMessages)
return sConfirmCode
根據官方文件,我們可以知道OnReplyMessage()函式有一個回傳值,型態為short int,當其為VARIANT_TRUE時,我們才算正常開通功能。
那到底VARIANT_TRUE是什麼呢?
VARIANT_BOOL這種型態,很明顯就是一個布林型態,但是給COM元件所使用的,實際上會是short的型態。
而VARIANT_TRUE其值會是什麼呢?我們可以透過以下文件查詢得知:
The VARIANT_BOOL type specifies Boolean values. This type is declared as follows: typedef short VARIANT_BOOL; The…docs.microsoft.com
我們可以知道VARIANT_TRUE相當於0xFFFF,而VARIANT_FALSE相當於0x0000。VARIANT_FALSE的部分我想不需要解說,就是整數0。但VARIANT_TRUE的整數是什麼呢?由於我們得轉換為short,是屬於signed整數,我們可以用二補數進行轉換,首先需要先轉為二進制:
0xFFFF
1111 1111 1111 1111 (轉為二進制)
1 111 1111 1111 1111 (分離符號與數字)
1 000 0000 0000 0000 (由符號位元為1得知為負數,需要將數字部分進行反轉)
1 000 0000 0000 0001 (最後補一回來)
-1 (轉為有號整數)
因此,我們就能得知VARIANT_TRUE值相當於-1。
這就是在OnReplyMessage()函式中,為何要回傳-1的原因。
接下來,我們需要註冊事件,透過comtypes.client.GetEvents()函式去進行綁定:
if __name__ == '__main__':
sk, skC, skQ, skR = capital_api_init(api_path)
capital_Login(account, password)
# event registration.
SKQuoteEvent = SKQuoteLibEvents()
SKQuoteLibEventHandler = comtypes.client.GetEvents(skQ, SKQuoteEvent)
SKReplyEvent = SKReplyLibEvents()
SKReplyLibEventHandler = comtypes.client.GetEvents(skR, SKReplyEvent)
...
好啦~~~,我們是跑看看吧,迫不及待了對不對。執行結果如下圖所示:
怎麼出錯了啊…
因為我們得要在登入之前,進行Reply事件的綁定呀~
所以我們修改一下上面的程式碼:
if __name__ == '__main__':
sk, skC, skQ, skR = capital_api_init(api_path)
# reply event registration before login.
SKReplyEvent = SKReplyLibEvents()
SKReplyLibEventHandler = comtypes.client.GetEvents(skR, SKReplyEvent)
capital_Login(account, password)
# event registration.
SKQuoteEvent = SKQuoteLibEvents()
SKQuoteLibEventHandler = comtypes.client.GetEvents(skQ, SKQuoteEvent)
...
現在執行看看吧~嘿嘿:
又出錯啦~~,全都照著步驟做了,怎麼會這樣呢?
這是因為我們先前介紹到使用comtypes時,有提到的一個重點。
由於comtypes.client.GetModule()函式會對指定的COM檔案,參考裡面所涵蓋的type library,進一步轉為一組Python module,基本上會放置於path\to\Lib\site-packages\comtypes\gen目錄底下。
我們可以在執行的時候,用debug功能進行中斷,查看當前使用到的元件是哪個版本的。如下圖所示(為何<api_path>變數是2.13.25版本的目錄呢?因為這張圖片我先前早就先擷取好了,只是苦無時間繼續往下編輯):
找出源頭之後,我們回過頭去找到目標檔案,果然發現還是取用到舊版的元件:
整個目錄結構如下面兩張圖所示:
這些檔案是我們在第一次執行時,會另存的一個暫存檔,會這樣做的好處是第二次(含)之後的執行,都不需要另外進行轉換的動作。
所以我們仔細看,主目錄裡面還有一個子目錄__pycache__,裡面便會存放Python在第一次執行主程式後,把相關依賴的函式庫或模組轉為bytecode進行儲存,以.pyc為副檔名的檔案存在。之所以會這樣做,也是為了讓Python這類的Interpreted Language可以在第二次與之後的執行更快速(簡單來說,就是不用再轉換一次bytecode的動作)。
所以只要COM元件指向不變,更版的動作並不會重新刷新這些暫存檔,往後我們又會去使用到這些舊的暫存code去執行,這顯然不行啊,我們需要把這些全部移除,並重新執行我們的python主程式,讓其自動產生一個新的暫存檔。
成功執行就會如下圖:
功能都與先前一樣,看起來也沒有什麼問題,我們並不需要另外再修改什麼地方。(不過,倒是聽說新版在獲取資料時,可能會漏資料,這個問題目前就聽天由命吧)
結論
其實更新版本應該是一件非常簡單的事情,但由於我們是自行串接,透過一些程式語言與套件進行開發,若對我們自己所使用的東西不那麼了解,那我們很可能會遇到所謂的邏輯錯誤。
無論如何,本篇完整介紹了一個在使用Python開發,並使用comtypes套件時,該如何進行群益API的版本更新。中間我們也詳細解析了會遇到的問題,並去解決它。
向您請教一下,群益API有盤中零股的報價嗎?
回覆刪除目前看文件應該是沒有這項功能,可以向群益官方論壇詢問(不過我有看到同名的帳號今天已有詢問),目前官方也回應說沒有。
刪除雖說撮合的機制與頻率不同,但理論上同樣商品零股的走勢與波動會接近原股票市場,若非作短期甚至高頻、配對交易的研究,應該還是能夠去解決這類型資料缺乏的問題。
J大您好,我又來問問題了
回覆刪除因為我對事件的處理並不是很了解
因為我已經把其他的Funtion都用進程處理了
但是開盤量時太太大,我的GUI直接卡住
如果我像要把報價放到進程裡該怎麼做呢??
有辦法嗎?
一般而言,GUI會卡住是因為繁重的工作在main loop(for GUI)執行,使得其他工作無法進行,像是短時間內多次更新畫面,以顯示新的訊息等等。這時,會使用multithreading搭配standard I/O的方式去減輕或解決main loop的卡頓情況。通常,用於撰寫GUI的套件或工具都會附帶有自己的non-blocking相關的方法來處理這樣的情形。
刪除而用multiprocessing來處理也不是不行,你要確定多個processes之間,能夠很好地交換資訊。但啟動新的process代價也不小,因此,如果是因為更新畫面的原因,基本上是使用multithreading的方式去解決。
你得將job要做的事情簡化,讓它功能單一一點,專心處理某一件事(用於找出並針對你要解決的根本原因來對症下藥),功能單純,多工的過程也就輕鬆得多,並且不是什麼都以多工的方式處理。
感謝J大詳細解釋
刪除真的是萬分感謝
我已經使用佇列將即時資訊用進程處理
但是會失真
還是希望能直接修改將OnNotifyQuote直接是進程去跑