2023年2月20日 星期一

使用 pythonnet 呼叫群益api

 多年前曾用想要用pythonnet,來載入群益的api ,但最後失敗了。這篇純粹是出自個人好奇,還是想了解是否能用 pythonnet 來載入 dll,最近找到可以使用的步驟了,在windows下 python 可以呼叫群益api,讓熟悉 C# 的開發者,又想用 python 的人多一種選擇。

    目前使用這個方法的好處:

  1. 在 juypterlab 下,可以顯示各個 function 參數有哪些,減少一直翻手冊的時間。
  2. callback 部屬比較直覺快速,但也因此比較冗長。
  3. 熟悉 C# 的人,可以加入 C# 相關的 reference, namespace 當作 python 的模組來使用。
  4. 其他券商的 dll 檔,也有機會用這套方法來呼叫? 

    壞處:

  1. 目前遇到狀況是 callback 出現錯誤時,python沒有任何錯誤通知,除錯比較麻煩。


我的安裝環境:

  • windows 10, 
  • 我使用的是python 3.10.5 
  • visual studio 2022 (只是為了使用它的 tlbimp.exe)

前置工作

1. 用 tlbimp.exe 將 SKCOM.dll 轉成  Type library

    要先將群益的SKCOM.dll 轉成  Type library,我有安裝 visual studio 2022,利用它附帶的 comandline prompt terminal ,下指令 tlbimp.exe 將 SKCOM.dll 轉成 typelib 形式,最終產生 SKCOMLib.dll 的檔案。之後就可以輕鬆用 pythonnet 的 clr 模組取用。




2. 安裝 pythonnet

    Python.NET 模組可以讓 python 使用者無縫整合 .NET Common Language Runtime (CLR)。它讓 Python 可以跟 CLR 互動, 甚至也可以反過來將 Python 包進 .NET 裡.
    在你的 python 環境下用 pip 安裝 pythonnet 即可。詳細操作請看 pythonnet 官網


    pip install pythonnet


使用 pythonnet 的 clr 模組呼叫 SKCOMAPI


# 使用 pythonnet 呼叫群益api
import clr
# AddReference,不用加副檔名 .dll
clr.AddReference(r"C:\skcom\CapitalAPI_2.13.41\x64\SKCOMLib") # 使用絕對路徑我個人偏好這個
# 加入參考後,利用 improt SKCOMLib 可以使用群益整個 skcomapi 的功能,
import SKCOMLib
# 也可以 from SKCOMlib import 部分功能
from SKCOMLib import SKCenterLib, SKReplyLib, SKQuoteLib, SKSTOCKLONG
# 推動eventloop用
import time
import pythoncom
# 使用 skcom 元件
skC = SKCenterLib()
skR = SKReplyLib()
skQ = SKQuoteLib()
#####################################################
# Configuration
ID = ""
PW = ""
# 欲報價的商品代號
StockNo = "1101,2330"
# working function
# resqest stock
def request_stock(page, stocks):
ncode, _ = skQ.SKQuoteLib_RequestStocks(page, stocks)
print(f"Request stock: {stocks}", skC.SKCenterLib_GetReturnCodeMessage(ncode))
#####################################################
# Event callback
# SKReplyLib 相關 callback
def OnReplyMessage(bstrUserID , bstrMessage, sConfirmCode):
# 這個一定要,Login 時會檢查 sConfirmCode 是否 == -1
sConfirmCode = -1
print("OnReplyMessage", bstrMessage)
return sConfirmCode
# SKQuoteLib 相關
def OnConnection(nKind, nCode):
"""回報連線報價伺服器狀態
nKind: 3001 連線報價伺服器
3002 離線報價伺服器
3003 連線成功,收到3003後,始可登錄報價商品
"""
print(f'skQ_OnConnection nKind= {nKind}', skC.SKCenterLib_GetReturnCodeMessage(nKind))
if nKind == 3003:
request_stock(1, StockNo)
def OnNotifyQuoteLONG(sMarketNo, nIndex):
"""報價"""
ts = SKSTOCKLONG()
ncode, ts = skQ.SKQuoteLib_GetStockByIndexLONG(sMarketNo, nIndex, ts)
print(ts.bstrStockName, "市價", ts.nClose/ 10**ts.sDecimal, "單量", ts.nTickQty)
#####################################################
# 綁定 event callback,需一個一個綁定,如果有比較快的方法請跟我分享 :)
# 請查閱SKCOMapi手冊有哪些 Callback,及相關參數
skR.OnReplyMessage += OnReplyMessage
skQ.OnConnection += OnConnection
skQ.OnNotifyQuoteLONG += OnNotifyQuoteLONG
print("綁定各元件的event callback")
####################################################
# Login
ncode = skC.SKCenterLib_Login(ID, PW)
print("Login", skC.SKCenterLib_GetReturnCodeMessage(ncode))
# EnterMonitor
ncode = skQ.SKQuoteLib_LeaveMonitor()
ncode = skQ.SKQuoteLib_EnterMonitorLONG()
print("EnterMonitor", skC.SKCenterLib_GetReturnCodeMessage(ncode))
# pump events for 30 seconds
print("Pumping events for 30s")
for i in range(30):
pythoncom.PumpWaitingMessages()
time.sleep(1)