群益API Python 程式架構:一支程式同時跑行情、下單、回報
前面每一篇的範例各自跑得動,合在一支程式就亂了:不知道先後順序,也不知道哪裡要等。行情來了就想下單,回報卻還沒就緒。斷線重連、訂閱、處理資料的程式碼散在各個事件裡,越寫越難追。測試時,也擔心不小心送出真單。
群益API 程式架構的重點只有一個:事件只記狀態、只收集資料;抽訊息、重連、訂閱、處理資料、下單,全部回到主迴圈做。下單一律經過模擬開關,預設模擬。
本文是〈群益API Python 入門〉的延伸,只負責把前面各篇接起來;每一步的細節,都連回原本說明它的那一篇。

目錄
群益API 程式從啟動到可以下單,要經過哪些步驟?
【環境】 環境檢查通過
【登入前】 GetModule → CreateObject(四個物件)→ GetEvents(每個物件一個事件類別)
(要切換環境時:SetAuthority)
Login
【登入後,三條線】
下單線: SKOrderLib_Initialize → ReadCertByID → GetUserAccount(帳號從 OnAccount 回來)
回報線: SKReplyLib_ConnectByID → 等 OnComplete(回補完成)
行情線: EnterMonitorLONG → 等 OnConnection 收到 3003 → 訂閱
【主迴圈】 抽送訊息 → 重連 → 訂閱 → 處理收集到的資料 → 需要時下單(經過模擬開關)每一步的細節:
| 步驟 | 細節見 |
|---|---|
| 環境檢查 | 〈群益API Python 環境檢查〉 |
| 物件、事件、登入前後的順序 | 〈群益API 的六個 COM 物件各管什麼〉 |
| 切換環境 | 〈群益API 測試環境是什麼〉 |
| 登入錯誤碼 | 〈群益API 登入錯誤怎麼判讀〉 |
| 下單線、回報線 | 〈群益API 下單初始化有哪 7 步〉 |
| 行情線、3003 | 〈群益API Python 入門〉、〈群益API 連線狀態碼怎麼解讀〉 |
| 商品代碼 | 〈群益API 商品代碼怎麼填〉 |
三條線之間,官方沒有規定先後;本文照下單、回報、行情的順序寫。
什麼時候可以開始下單?建議等回報線收到 OnComplete(回補完成)再送單,原因見〈群益API 下單初始化有哪 7 步〉。你的策略如果需要報價,再加上行情就緒這個條件;行情就緒是策略需要報價時的條件,不是下單函式的前提。
主迴圈怎麼分工?事件記狀態,主迴圈做事
事件裡只做兩種事:改狀態旗標、把資料放進收集區。這是〈群益API 事件回呼跑在哪個執行緒〉回呼紀律的應用。
主迴圈每一圈做這些事:
- 抽送訊息:呼叫
pythoncom.PumpWaitingMessages(),讓事件進來,見〈Python 接群益API 收不到事件〉。 - 行情斷線就重連:收到
3002的旗標後,在事件之外重新連線,見〈群益API 斷線怎麼重連〉。 - 行情就緒就訂閱一次:不論成敗都只送一次,避免每一圈重試;斷線時把「已訂閱」旗標清掉,重連就緒後再送一次。
- 回報斷線就等幾秒再重連:同樣見〈群益API 斷線怎麼重連〉。
- 處理收集到的資料:報價用
GetStockByIndexLONG取快照;回報字串以980開頭時原樣記錄,見〈群益API 委託回報怎麼解析〉;帳號資料收下來留給下單用。 - 需要下單時,呼叫經過模擬開關的
send_order()。
跟入門篇的寫法有一點不同:入門篇在 OnNotifyQuoteLONG 事件裡直接取快照,這裡改成事件只收集索引、主迴圈再取。兩種寫法都可以;這裡這樣寫,是為了讓所有事件都只做最輕量的事。主迴圈取到的,是取的當下的快照。
下單一律走模擬開關
骨架用的是〈群益API 測試環境是什麼〉模擬開關四點裡的前兩點:
- 全域的開關,預設是模擬。
- 模擬時,在呼叫下單函式之前攔截,不送出委託。
刪改單在模擬時的處理、切換到真單的明確動作,骨架沒有寫,做法見同一篇。
另外,真單的路徑多一道檢查:回報還沒就緒,就不送單。骨架的主流程也沒有呼叫 send_order();什麼時候下單,由你自己的策略決定。
為什麼用單執行緒?
在這個單執行緒的骨架下,事件和主迴圈都在同一條執行緒上,所有 COM 呼叫也都在這條執行緒,不需要另外加鎖。
在 STA 下,事件會在你抽送訊息的那條執行緒上進來,見〈Python 接群益API 收不到事件〉。之後如果想把運算交給其他執行緒,COM 呼叫要留在建立物件的那條執行緒上,見〈群益API 事件回呼跑在哪個執行緒〉。
群益API Python 整合骨架
# 群益API Python 整合骨架:行情、下單、回報同一支程式(下單預設模擬)
# 環境:Windows、Python 64 位元、已註冊 64 位元元件、pip install comtypes pywin32
import os
import time
import pythoncom
import comtypes.client
DLL_PATH = r"C:\SKCOM\x64\SKCOM.dll" # 改成你註冊的元件路徑
USER_ID = os.environ.get("CAPITAL_ID", "") # 帳密從環境變數讀,不寫死
PASSWORD = os.environ.get("CAPITAL_PWD", "")
SIMULATION = True # 模擬開關:預設模擬,不呼叫下單函式
comtypes.client.GetModule(DLL_PATH)
import comtypes.gen.SKCOMLib as sk
center = comtypes.client.CreateObject(sk.SKCenterLib, interface=sk.ISKCenterLib)
reply = comtypes.client.CreateObject(sk.SKReplyLib, interface=sk.ISKReplyLib)
quote = comtypes.client.CreateObject(sk.SKQuoteLib, interface=sk.ISKQuoteLib)
order = comtypes.client.CreateObject(sk.SKOrderLib, interface=sk.ISKOrderLib)
# 事件裡只記狀態、只收集資料;真正的處理在主迴圈
state = {"quote_ready": False, "quote_down": False, "subscribed": False,
"reply_ready": False, "reply_down_at": None}
inbox = [] # 事件收集到的資料,主迴圈再處理
accounts = [] # 帳號資料留給下單使用,不要印到主控台或 log
class ReplyEvents:
def OnReplyMessage(self, bstrUserID, bstrMessage):
return -1 # 登入前必須掛上,回 -1
def OnComplete(self, bstrUserID):
state["reply_ready"] = True # 回補完成,之後才開放送單
def OnNewData(self, bstrUserID, bstrData):
inbox.append(("reply", bstrData)) # 回報字串交給主迴圈解析
def OnSolaceReplyDisconnect(self, bstrUserID, nErrorCode):
state["reply_ready"] = False
state["reply_down_at"] = time.time() # 不在事件裡重連
class QuoteEvents:
def OnConnection(self, nKind, nCode):
if nKind == 3003:
state["quote_ready"] = True
elif nKind == 3002:
state["quote_ready"] = False
state["subscribed"] = False
state["quote_down"] = True # 不在事件裡重連
def OnNotifyQuoteLONG(self, sMarketNo, nIndex):
inbox.append(("quote", (sMarketNo, nIndex)))
class OrderEvents:
def OnAccount(self, bstrLogInID, bstrAccountData):
inbox.append(("account", bstrAccountData))
# 每個物件一個事件類別;回傳值要存起來
reply_conn = comtypes.client.GetEvents(reply, ReplyEvents())
quote_conn = comtypes.client.GetEvents(quote, QuoteEvents())
order_conn = comtypes.client.GetEvents(order, OrderEvents())
def check(name, code):
print(name, code, center.SKCenterLib_GetReturnCodeMessage(code))
return code == 0
def send_order(order_obj):
"""策略要下單時呼叫這裡;模擬時根本不呼叫下單函式"""
if SIMULATION:
print("模擬單,未送出")
return 0, "SIM"
if not state["reply_ready"]:
return -1, "回報尚未就緒,不送單"
message, code = order.SendFutureOrderCLR(USER_ID, False, order_obj)
return int(code), message
# 1. 登入
if not check("Login", center.SKCenterLib_Login(USER_ID, PASSWORD)):
raise SystemExit("登入失敗,判讀見登入錯誤碼那篇")
# 2. 下單線
check("SKOrderLib_Initialize", order.SKOrderLib_Initialize())
check("ReadCertByID", order.ReadCertByID(USER_ID))
check("GetUserAccount", order.GetUserAccount()) # 帳號從 OnAccount 回來
# 3. 回報線
check("SKReplyLib_ConnectByID", reply.SKReplyLib_ConnectByID(USER_ID))
# 4. 行情線
check("EnterMonitorLONG", quote.SKQuoteLib_EnterMonitorLONG())
# 5. 主迴圈:抽送訊息、處理收集到的資料、在事件之外重連與訂閱
REPLY_WAIT_SECONDS = 5 # 示意值,依你的程式調整;官方只說「等待數秒」
while True:
pythoncom.PumpWaitingMessages()
if state["quote_down"]:
state["quote_down"] = False
quote.SKQuoteLib_EnterMonitorLONG()
if state["quote_ready"] and not state["subscribed"]:
page_no, code = quote.SKQuoteLib_RequestStocks(1, "TX00")
check("RequestStocks", code)
state["subscribed"] = True # 不論成敗只送一次;失敗看訊息,重連就緒後才會再送
down_at = state["reply_down_at"]
if down_at and time.time() - down_at >= REPLY_WAIT_SECONDS:
state["reply_down_at"] = None
if reply.SKReplyLib_IsConnectedByID(USER_ID) == 0:
reply.SKReplyLib_ConnectByID(USER_ID)
while inbox:
kind, data = inbox.pop(0)
if kind == "quote":
stock, code = quote.SKQuoteLib_GetStockByIndexLONG(data[0], data[1], sk.SKSTOCKLONG())
if code == 0:
print(stock.bstrStockNo, stock.nClose / (10 ** stock.sDecimal))
elif kind == "reply":
fields = data.split(",")
if fields[0] == "980":
print("回報原樣記錄:", data) # 980 開頭不套欄位表
# 其他欄位的解析見委託回報那篇
elif kind == "account":
accounts.append(data) # 帳號資料留給下單使用,不要印到主控台或 log
time.sleep(0.05)幾點說明:
- 這段骨架的每個呼叫,都在前面各篇說明過;整合起來,需要你用自己的帳號實際跑一次。
SIMULATION = True時,策略呼叫send_order()不會送出任何委託。- 帳號密碼從環境變數讀,不寫死在程式裡;帳號資料只收下來留給下單用,不印到主控台或 log。
- 各步驟失敗時要不要中止,依你的程式設計;骨架只印出結果。
REPLY_WAIT_SECONDS = 5是示意值:官方只說「等待數秒」,請依你的程式調整。inbox只是示意:收集區的資料結構,依你的程式設計。- 回傳值一律直接拆包,原因見〈群益API Python 回傳值為什麼是 list〉;下單失敗的判讀,見〈群益API 下單回傳 980 怎麼讀〉。
群益API 程式架構的 4 個誤解
- 誤解: 行情就緒就可以下單。其實: 建議等回報線收到
OnComplete(回補完成)再送單;行情就緒是策略需要報價時才加上的條件。 - 誤解: 事件來了,就在事件裡把事情做完。其實: 事件裡只記狀態、收集資料,重連、訂閱、下單都回到主迴圈做。
- 誤解: 測試時用小口數下單就好。其實: 在程式裡做模擬開關、預設模擬,才能確保不會送出真單。
- 誤解: 重連之後,訂閱會自己恢復。其實: 斷線時清掉「已訂閱」的旗標,重連就緒後重新送出訂閱。
常見問題
群益API 的行情、下單、回報要照什麼順序初始化?
官方沒有規定三條線的先後;本文照下單、回報、行情的順序寫。各條線自己的順序與就緒訊號,見〈群益API 的六個 COM 物件各管什麼〉。
什麼時候才能開始送單?
建議等回報線收到 OnComplete(回補完成)再送單。你的策略如果需要報價,再加上行情收到 3003 這個條件。
怎麼確保測試時不會送出真單?
在程式裡做模擬開關,預設模擬:模擬時在呼叫下單函式之前攔截,不送出委託。切換到真單要有明確的動作。
程式要結束時要做什麼?
先停止送單、停止新的訂閱。另外,官方文件沒有登出函式。見〈群益API 斷線怎麼重連〉的常見問題。
風險揭露
期貨與選擇權屬高槓桿商品,價格波動可能造成超過原始保證金的損失,交易人須自負交易責任。本文為程式開發技術教學,說明群益API Python 程式的整體架構,不構成投資建議,也不保證任何交易結果。程式化交易並不降低市場風險,程式錯誤可能造成非預期的委託行為;本文的骨架預設模擬、不含任何策略,正式使用前請自行撰寫並充分測試。
投資人教育資源
| 機構 | 資源 |
|---|---|
| 臺灣期貨交易所(TAIFEX) | 期貨及選擇權數位學習網 |
| 證券暨期貨市場發展基金會 | 證券期貨市場教育推廣 |
| 金融智慧網 | 金管會金融知識平台 |
| 中華民國期貨業商業同業公會 | 期貨業法規與宣導 |
| 證券投資人及期貨交易人保護中心 | 投資人保護與申訴 |
延伸閱讀
- 〈群益API Python 入門〉——從安裝到收到第一筆報價。
- 〈群益API 的六個 COM 物件各管什麼〉——登入前一段、登入後三條線。
- 〈群益API 測試環境是什麼〉——程式層的模擬開關。
- 〈群益API 斷線怎麼重連〉——報價與回報的重連步驟。
參考資料
- 各函式與事件的宣告,依群益官方元件說明文件;逐項核對見本系列各篇。
- 三條線的撰寫順序、等回報就緒再送單、事件只收集的分工:本系列各篇的做法與通用工程做法,非官方規定。
- 整合骨架:已做語法與流程的檢查,登入之後的整段沒有整合實跑紀錄。
免責聲明
本文章僅作為群益API實作經驗分享,不構成投資建議,且策略及程式皆應自行撰寫。期貨及衍生性金融商品交易屬高風險投資,請謹慎評估自身風險承擔能力。







