2022年5月9日 星期一

群益 skcom api 海期報價、下單及回報範例

群益海期報價跟下單跟api手冊上寫的有點出入,手冊也沒有明確表示下單的流程,經過一番試誤,可以報價也可以下單了。實在是太少碰海期相關的 api,每次想用的時候都重新經歷一次錯誤,因此寫下來留個紀錄好參考。真的很少用,可能有不少地雷,因此僅供參考,也歡迎大家分享曾經遇過的問題。

幾個容易有錯誤的地方

1. 海期商品與交易所的報價代號跟下單代號有些是不同的,請用 GetOverseaProductDetail 來查詢正確代號。

2. 報價代碼寫法是  "交易所代碼1,商品代碼1#交易所代碼2,商品代碼2", 不同商品間以#字號隔開

3. 海期商品連線報價主機後,只會回應 3001,之後即可查詢報價

3. 海期委託單物件填寫商品代碼跟 GetOverseaProductDetail 查詢的稍有出入,需要加工處理。例如微型小道瓊下單代碼為 MYM_202206,填入委託單物件時事拆成兩個參數,要分別填入商品代碼及年月, 如 bstrStockNo = "MYM" 及 bstrYearMonth = "202206"

4. 要能下單要有幾項前置作業, SKOrderLib_Initialize >> ReadCertByID >> GetUserAccount >> SKOrderLib_LoadOSCommodity

5. "期貨API下單同意書"要記得簽,才能進行海期報價,我都用手機掌中財神 app,下方有個同意書專區,把裡面能簽的都簽了。

6. 有網友詢問如何使用 GetOverSeaFutureOpenInterestGW,手冊寫經由 OnOverSeaFutureOpenInterestGW 回報,測試後發現並沒有反應。深查後發現手冊坑人,原來是經由 OnOFOpenInterestGWReport 來做回報,也一併加入本範例供參考。

Notebook 範例版 jupyter notebook 

py 範例版,僅在 spyder IDE 下測試過

# # 海期報價及下單範例
import pythoncom
import asyncio
import datetime
import pandas as pd
import comtypes.client as cc
# 只有第一次使用 api ,或是更新 api 版本時,才需要呼叫 GetModule
# 會將 SKCOM api 包裝成 python 可用的 package ,並存放在 comtypes.gen 資料夾下
# 更新 api 版本時,記得將 comtypes.gen 資料夾 SKCOMLib 相關檔案刪除,再重新呼叫 GetModule
cc.GetModule('C:\\skcom\\CapitalAPI_2.13.37\\x64\\SKCOM.dll')
import comtypes.gen.SKCOMLib as sk
# login ID and PW
# 身份證
ID = 'A123456789'
# 密碼
PW = 'ThisIsUrPW'
print(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S,"), 'Set ID and PW')
# # 建立 event pump and event loop
# 新版的jupyterlab event pump 機制好像有改變,因此自行打造一個 event pump機制,目前在 jupyterlab 環境下使用,也有在 spyder IDE 下測試過,都可以正常運行
# working functions, async coruntime to pump events
async def pump_task():
'''在背景裡定時 pump windows messages'''
while True:
pythoncom.PumpWaitingMessages()
# 想要反應更快 可以將 0.1 取更小值
await asyncio.sleep(0.1)
# get an event loop
loop = asyncio.get_event_loop()
pumping_loop = loop.create_task(pump_task())
print(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S,"), "Event pumping is ready!")
# # 建立 event handler
# 建立物件,避免重複 createObject
# 登錄物件
if 'skC' not in globals(): skC = cc.CreateObject(sk.SKCenterLib, interface=sk.ISKCenterLib)
# 下單物件
if 'skO' not in globals(): skO = cc.CreateObject(sk.SKOrderLib , interface=sk.ISKOrderLib)
# 海期報價物件
if 'skOSQ' not in globals(): skOSQ = cc.CreateObject(sk.SKOSQuoteLib , interface=sk.ISKOSQuoteLib)
# 回報物件
if 'skR' not in globals(): skR = cc.CreateObject(sk.SKReplyLib, interface=sk.ISKReplyLib)
# 建立事件處理類別
# SKOSQ event handler
class skOSQ_events:
def __init__(self):
self.OverseaProductsDetail = []
def OnConnect(self, nKind, nCode):
'''連線海期主機狀況回報'''
print(f'skOSQ_OnConnect nCode={nCode}, nKind={nKind}')
def OnOverseaProductsDetail(self, bstrValue):
'''查詢海期/報價下單商品代號'''
if "##" not in self.OverseaProductsDetail:
self.OverseaProductsDetail.append(bstrValue.split(','))
else:
print("skOSQ_OverseaProductsDetail downloading is completed.")
def OnNotifyQuoteLONG(self, sIndex):
'''requestStock 報價回報'''
# 儘量避免在這裡使用繁複的運算,這裡僅在 console 端印出報價
ts = sk.SKFOREIGNLONG()
nCode = skOSQ.SKOSQuoteLib_GetStockByIndexLONG(sIndex, ts)
print(ts.bstrExchangeNo, ts.bstrStockNo, ts.nClose, ts.nTickQty)
# SKReplyLib event handler
class skR_events:
def OnReplyMessage(self, bstrUserID, bstrMessage):
'''API 2.13.17 以上一定要返回 sConfirmCode=-1'''
sConfirmCode = -1
print('skR_OnReplyMessage ok')
return sConfirmCode
def OnNewData(self, bstrUserID, bstrData):
'''委託單回報'''
print("skR_OnNewData", bstrData)
# SKOrderLib event handler
class skO_events:
def __init__(self):
self.TFAcc = []
def OnAccount(self, bstrLogInID, bstrAccountData):
strI = bstrAccountData.split(',')
# 找出期貨帳號
if len(strI) > 3 :
if strI[0] == 'TF' :
# 分公司代碼 + 期貨帳號
self.TFAcc = strI[1] + strI[3]
def OnOFOpenInterestGWReport(self, bstrData):
# 回報海期的 OI 資料
print(bstrData)
# # 建立 event 跟 event handler 的連結
# Event sink, 事件實體化
EventOSQ = skOSQ_events()
EventR = skR_events()
EventO = skO_events()
# 建立 event 跟 event handler 的連結
ConnOSQ = cc.GetEvents(skOSQ, EventOSQ)
ConnR = cc.GetEvents(skR, EventR)
ConnO = cc.GetEvents(skO, EventO)
# # 登入及各項初始化作業
# login
print('Login', skC.SKCenterLib_GetReturnCodeMessage(skC.SKCenterLib_Login(ID,PW)))
# 海期商品初始化
nCode = skOSQ.SKOSQuoteLib_Initialize()
print("SKOSQuoteLib_Initialize", skC.SKCenterLib_GetReturnCodeMessage(nCode))
# 下單前置至步驟
# 1. 下單初始化
nCode = skO.SKOrderLib_Initialize()
print("Order Initialize", skC.SKCenterLib_GetReturnCodeMessage(nCode))
# 2. 讀取憑證
nCode = skO.ReadCertByID(ID)
print("ReadCertByID", skC.SKCenterLib_GetReturnCodeMessage(nCode))
# 3. 取得海期帳號
nCode = skO.GetUserAccount()
print("GetUserAccount", skC.SKCenterLib_GetReturnCodeMessage(nCode), EventO.TFAcc)
# 4. 連線委託回報主機
nCode = skR.SKReplyLib_ConnectByID(ID)
print("Connect to ReplyLib server", skC.SKCenterLib_GetReturnCodeMessage(nCode))
# # 登入海期報價主機,確認 OnConnect 出現 3001 回報後始可進行下列步驟
# 以下皆以手動輸入
# 5. 登入海期報價主機
nCode = skOSQ.SKOSQuoteLib_EnterMonitorLONG()
print('SKOSQuoteLib_EnterMonitor()', skC.SKCenterLib_GetReturnCodeMessage(nCode))
# # 下單前需要下載海期商品,才能下單
# 不然會報 1035 錯誤碼
# 6. 登入海期報價主機後,等確認 OnConnect 出現 3001 後,再下載海期商品
nCode = skO.SKOrderLib_LoadOSCommodity()
print('SKOrderLib_LoadOSCommodity', skC.SKCenterLib_GetReturnCodeMessage(nCode))
# # 查詢海期交易所及商品報價與下單代碼
# 等 OnConnect 出現 3001 回報後,可以查詢海期交易所及交易商品代號
# 查詢詳細交易所及商品代號,注意海期下單與報價代號有些不同
EventOSQ.OverseaProductsDetail = []
nCode = skOSQ.SKOSQuoteLib_GetOverseaProductDetail(1)
print("GetOverseaProductDetail", skC.SKCenterLib_GetReturnCodeMessage(nCode))
print("交易所代碼, 交易所名稱, 商品報價代碼, 商品名稱, 交易所下單代碼, 商品下單代碼, 最後交易日")
print(EventOSQ.OverseaProductsDetail[5])
print(EventOSQ.OverseaProductsDetail[-2])
# 下單代碼
# 離開海期報價主機,有需要再使用
# nCode = skOSQ.SKOSQuoteLib_LeaveMonitor()
# print(nCode, skC.SKCenterLib_GetReturnCodeMessage(nCode))
# # 海期報價範例
# 登陸海期商品報價, 格式為 "交易所代碼,商品代碼",不同商品用#隔開,請利用
# GetOverseaProductDetail 查詢
# 登陸海期商品報,接收 callback 為 EventOSQ 的 OnNotifyQuoteLONG
# 注意熱門商品報價頻率會很高,要手動清除,不然 jupterlab 頁面會愈來愈慢
code = skOSQ.SKOSQuoteLib_RequestStocks(1, "CBOT,MYM0000#TCE,JCO2205")
print(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S,"), "RequestStocks", skC.SKCenterLib_GetReturnCodeMessage(code[1])
# # 海期下單物件 OVERSEAFUTUREORDER
# 委託價分子,這是海期商品小數點的部位,可以參考 https://www.order-master.com/doc/topic/54/
# 建立海期委託單物件
# 詳細參數請參考 api 手冊,這裡僅示範可以下單所需的參數
# 以下參數我是先用 api 附的 SKCOMtester.exe 測試,直到可以送單所測出來需要的參數
# 注意 bstr開頭的參數都要以文字型態帶入,特別是 委託價 (bstrOrder),委託價分子(bstrOrderNumerator)
# 根據 GetOverseaProductDetail 取得的下單代碼,
# 如 芝加哥交易所的微型小道瓊期貨,交易所代碼是 CBT, 商品下單代碼是 MYM_202206,
# 但 OVERSEAFUTUREORDER 物件的參數,要再另外拆成 海外期權代號(bstrStockNo) 及 近月商品年月(bstrYearMonth)
# 要將 MYM_202206 拆成 MYM 及 202206
fo = sk.OVERSEAFUTUREORDER()
fo.bstrFullAccount = EventO.TFAcc # 海期帳號,分公司代碼+帳號7碼
fo.bstrExchangeNo = "CBT" # 交易所代碼。
fo.bstrStockNo = "MYM" # 海外期權代號。
fo.bstrYearMonth = "202206" # 近月商品年月( YYYYMM) 6碼
# fo.bstrYearMonth2 # 遠月商品年月( YYYYMM) 6碼 {價差下單使用}
fo.bstrOrder = "31500" # 委託價。
fo.bstrOrderNumerator = "0" # 委託價分子。也就是小數點的部位
# fo.bstrTrigger # 觸發價。
# fo.bstrTriggerNumerator # 觸發價分子。
fo.sBuySell = 0 # 0:買進 1:賣出
# {價差商品,需留意是否為特殊商品-近遠月前的「+、-」符號}
fo.sNewClose = 0 # 新/平倉,0:新倉 {目前海期僅新倉可選}
fo.sDayTrade = 0 # 當沖 0:否, 1:是;{海期價差單不提供當沖}
# 可當沖商品請參考交易所規定。
fo.sTradeType = 0 # 0:ROD 當日有效單; 1:FOK 立即全部成交否則取消; 2:IOC 立即成交否則取消(可部分成交)
# {限價單LMT可選ROD/IOC/FOK,其餘單別固定ROD}
fo.sSpecialTradeType = 0 # 0:LMT 限價單 1:MKT 2:STL 3.STP
fo.nQty = 1 # 交易口數。
# 海期下單 SendOverSeaFutureOrder(bstrLogInID, bAsyncOrder, pOrder)
msg, nCode = skO.SendOverSeaFutureOrder(ID, 0, fo)
print(msg, skC.SKCenterLib_GetReturnCodeMessage(nCode))
## 測試 GetOverSeaFutureOpenInterestGW
# 手冊寫 OnGetOverSeaFutureOpenInterestGW 來回報是錯誤的,應該是 OnOFOpenInterestGWReport 來回報的
ncode = skO.GetOverSeaFutureOpenInterestGW(ID, EventO.TFAcc, 2)
print('GetOverSeaFutureOpenInterestGW', skC.SKCenterLib_GetReturnCodeMessage(ncode))

25 則留言:

  1. Eason大大,您好,請問那裡有群益的海期的範例呢?及說明文件?謝謝

    回覆刪除
  2. 簡單版的就我上面寫的,我這裡沒有用GUI介面,應該很簡潔了,已經包括報價、下單、回報等功能了。嘗試理解一下怎麼用 comtype 去包 skcomapi,怎麼做 event handler,跟怎麼 pump event,剩下的就去翻 api 手冊。官方版的好像沒有海期的 python 範例,但你可以參考官方的其他範例程式,有附上該呼叫的程式碼,雖然用的程式語言不同,但你還是可參考它呼叫的流程跟副程式有哪些,我自己是交叉比對,慢慢摸索的。有那裡卡住了,也可以留言給我,一起討論囉。

    回覆刪除
  3. Eason大大,您好
    目前有遇到一個想請教您,就是我可以抓出海期的商品, 但要登陸抓取海期商品報價 用 skOSQ.SKOSQuoteLib_RequestStocks(0,"CBT,MYM_202212") 或
    skOSQ.SKOSQuoteLib_RequestStocks(1,"CBOT,MYM2212")
    都沒有print出商品的即時報價,請問可能是那邊出問題了?謝謝

    回覆刪除
  4. 我用 skOSQ.SKOSQuoteLib_RequestStocks(1,"CBOT,MYM2212") 可以拿到報價。是參照我上面的code嗎? 如果一個字都沒有改的話,我猜可能是173行那邊,我原本是要留給大家手動操作的,會斷線報價伺服器,以便不時之需,我現在把他給關了,你可以再試試看。

    回覆刪除
  5. Eason大大,您好
    我的問題已經解決,可以正常報價了,謝謝
    但和173行那邊沒關係,謝謝幫忙

    回覆刪除
  6. Eason大大, 您好
    請問一下,群益海期報價好像沒看到ts的欄位,請問這樣是否就無法將即時報價tick轉分k?
    謝謝

    回覆刪除
    回覆
    1. 還是可以喔,OnNotifyTicksNineDigitLONG 的 nIndex, nPtr, 帶入SKOSQuoteLib_GetTickNineDigitLONG(nIndex, nPtr, pSKTick),再從 pSKTick 取出報價來組分K。這樣有看懂嗎??

      刪除
    2. Eason大大, 您好
      不好意思,有點不太理解,OnNotifyTicksNineDigitLONG 不是像OnNotifyQuoteLONG觸發事件會回報資料?
      我看手冊void OnNotifyTicksNineDigitLONG ([in] LONG nIndex, [in] LONG nPtr, [in] LONG nDate, [in] LONG nTime, [in] LONGLONG nClose, [in] LONG nQty);要帶6個參數
      是否請Eason大大範例說明,謝謝

      刪除
    3. 可以參考一下我台股tick報價那篇,用法類似。群益後來弄了 LONG系列,又多了 nineDigitLONG 我有點搞混了,要花點時間整理

      刪除
    4. 謝謝Eason大大, 我再試試看

      刪除
    5. 弄了個範例,你參考看看 https://easontseng.blogspot.com/2022/11/tick.html?m=1

      刪除
    6. 非常感謝Eason大大幫忙,幫助良多

      刪除
  7. Eason大大,您好
    請問 在訂閱ticks報價時會出現
    Requesting ticks, CBOT,YM2212
    SK_SUBJECT_TICK_STOCK_NOT_FOUND
    請問你也會嗎?謝謝

    回覆刪除
  8. Eason大大,
    不好意思,請忽略上個問題,要請問
    EventOSQ.ticks 在原本的class好像沒有ticks函數,要請問Eason大大這部份怎麼實作,謝謝

    回覆刪除
    回覆
    1. 上面那個連結海期ticks報價範例裡面有,你再看一下

      刪除
  9. Eason大大,您好
    謝謝解惑,已經可以了,感謝

    回覆刪除
  10. Eason大大,您好
    有一事請教一下就是我如果要同時多個海期商品報價的話
    用thread 加上 pythoncom.PumpWaitingMessages(),就接收不到資訊源的報價,想請問是不能這樣使用嗎?謝謝

    回覆刪除
    回覆
    1. 是的, 不能放在 thread 裡面, 要在 main thread 裡

      刪除
  11. Eason大大,您好
    請問如果要同時訂閱多個海期商品報價的話,
    請問有什麼可以建議的嗎?
    謝謝

    回覆刪除
    回覆
    1. 用 井號 將報價代碼分開,我的測試可以報到100檔, 像這樣 skOSQ.SKOSQuoteLib_RequestStocks(1, "CBOT,YM0000#CBOT,YM2303#CBOT,YM2306#CBOT,YM2309#CBOT,YM2303/2306#CBOT,MYM0000#CBOT,MYM2303#CBOT,MYM2306#CBOT,MYM2309#CBOT"), 這樣還不夠嗎?

      刪除
    2. Hi Eason大,
      您好, 我試用
      nCode = skOSQ.SKOSQuoteLib_RequestTicks(1, "CBOT,YM2303#CBOT,YM2306")
      會顯示”SK_SUBJECT_TICK_STOCK_NOT_FOUND”
      我之前試是1個page只能配一個商品,大概是我那邊用錯了
      我再試試,謝謝

      刪除
    3. 報價有兩種,RequestStock跟RequestTicks, 看你需求來使用。RequestTicks 一次只能填一頁一檔,所以你要一檔一檔的去請求。群益有限制請求 Tick 檔數,當你看到有 3027 出現時,表示你目前最大的可以請求檔數,試試下面, strCode 是 list 放一檔一檔的報價商品名,page 放 0 好像系統會自動幫你配,當超過上限時就會出現 3027 錯誤
      strCode = ['CBOT,YM0000', 'CBOT,YM2303', 'CBOT,YM2306', 'CBOT,YM2309', 'CBOT,YM2303/2306', 'CBOT,MYM0000']

      for page, code in enumerate(strCode):
      print(skOSQ.SKOSQuoteLib_RequestTicks(page+1, code))

      刪除
    4. 謝謝Eason大大回覆, 我想請教的是
      如果要幾乎同時計算多商品的分K
      是要用convert_to_kline(query_stock="MYM2303", freq="1T")
      帶入不同的query_stock="MYM2303",在同一個while loop內執行可行?
      謝謝

      刪除
    5. 轉換多商品 tick to kline放在最後,參考一下,不知道符不符合你的需求

      刪除
    6. 謝謝Eason大大,我再試試,謝謝

      刪除