群益API行情串接(五)


像股票這樣的金融商品百百種,加上市場的變化性與多樣的市場規則,使得這類的分析總是相當困難。但相關數據取得與蒐集的管道也相當稀少,甚至難以整合與上手。國內幾家券商提供之API服務便顯得相當地重要。

前言

唉~時間過得很快阿。平常總是抽不出時間把自己所見、所聞、所學,進行整理,更新到部落格上。今日收到一則簡訊,猛然一驚,群益API必須得要更版啦~_~。

我們廢話不多說,趕緊上工。

本篇將不會特別詳細介紹元件註冊,我們注重在群益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.28版本的出錯:

我們可以發現錯誤訊息稍微不一樣,到底改了哪些東西呢?這倒是不那麼重要,我們知道我們必須先註冊公告

不過,令我疑惑的倒是,官方文件提到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其值會是什麼呢?我們可以透過以下文件查詢得知:

我們可以知道VARIANT_TRUE相當於0xFFFF,而VARIANT_FALSE相當於0x0000VARIANT_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的版本更新。中間我們也詳細解析了會遇到的問題,並去解決它。

5 則留言:

  1. 向您請教一下,群益API有盤中零股的報價嗎?

    回覆刪除
    回覆
    1. 目前看文件應該是沒有這項功能,可以向群益官方論壇詢問(不過我有看到同名的帳號今天已有詢問),目前官方也回應說沒有。
      雖說撮合的機制與頻率不同,但理論上同樣商品零股的走勢與波動會接近原股票市場,若非作短期甚至高頻、配對交易的研究,應該還是能夠去解決這類型資料缺乏的問題。

      刪除
  2. J大您好,我又來問問題了
    因為我對事件的處理並不是很了解

    因為我已經把其他的Funtion都用進程處理了
    但是開盤量時太太大,我的GUI直接卡住

    如果我像要把報價放到進程裡該怎麼做呢??
    有辦法嗎?

    回覆刪除
    回覆
    1. 一般而言,GUI會卡住是因為繁重的工作在main loop(for GUI)執行,使得其他工作無法進行,像是短時間內多次更新畫面,以顯示新的訊息等等。這時,會使用multithreading搭配standard I/O的方式去減輕或解決main loop的卡頓情況。通常,用於撰寫GUI的套件或工具都會附帶有自己的non-blocking相關的方法來處理這樣的情形。
      而用multiprocessing來處理也不是不行,你要確定多個processes之間,能夠很好地交換資訊。但啟動新的process代價也不小,因此,如果是因為更新畫面的原因,基本上是使用multithreading的方式去解決。
      你得將job要做的事情簡化,讓它功能單一一點,專心處理某一件事(用於找出並針對你要解決的根本原因來對症下藥),功能單純,多工的過程也就輕鬆得多,並且不是什麼都以多工的方式處理。

      刪除
    2. 感謝J大詳細解釋
      真的是萬分感謝
      我已經使用佇列將即時資訊用進程處理
      但是會失真
      還是希望能直接修改將OnNotifyQuote直接是進程去跑

      刪除