群益API Tick 回補是什麼?盤中啟動收到舊成交的區分方式
盤中啟動程式,一訂閱就湧進一大批 Tick,時間全都是今天稍早的。策略被觸發了好幾次,成交量算出來也比實際大。
這不是元件出錯,是群益API Tick 回補在做它該做的事。 問題在於你的程式把回補送來的舊成交,當成了剛剛發生的。

目錄
群益API Tick 回補是什麼?兩個事件,一個舊一個新
用 SKQuoteLib_RequestTicks 訂閱成交明細時,官方在「相關通知事件」欄列了三個事件:
即時Tick由OnNotifyTicksLONG事件通知。 Tick回補由 OnNotifyHistoryTicksLONG事件通知。 最佳五檔由OnNotifyBest5LONG事件通知。
而 OnNotifyHistoryTicksLONG 的官方說明是:
當首次索取個股成交明細,此事件會回補當天Tick。
所以盤中啟動、第一次訂閱某檔商品時,你會先收到當天稍早、已經成交的 Tick,而且是從另一個事件進來的。
關鍵在這裡:兩個事件的參數完全相同。 光看參數分不出哪一筆是舊的、哪一筆是新的,只能靠「是哪一個事件送來的」來分。 如果兩個事件接到同一個處理函式,當天稍早的成交就會被當成剛剛發生的。
我們的做法是分流:回補的 Tick 只拿來重建走勢與統計,不觸發即時的策略判斷。這是我們的選擇,你也可以有別的用法——重點是兩個事件要分得開。
如果你讀過〈群益API 下單初始化有哪 7 步〉會覺得眼熟:回報通道連上之後也會先回補。連上線就先給你一批舊資料,是這套 API 反覆出現的行為。
不需要回補,可以改用 RequestLiveTick 嗎?
可以。官方另有一個訂閱函式 SKQuoteLib_RequestLiveTick,備註寫的是(節錄):
與SKQuoteLib_RequestTicks不同,不會回補歷史成交明細資料,請擇一使用
| 函式 | 官方列的相關通知事件 | 回補 |
|---|---|---|
SKQuoteLib_RequestTicks | OnNotifyTicksLONG、OnNotifyHistoryTicksLONG、OnNotifyBest5LONG | 首次索取時回補當天 Tick |
SKQuoteLib_RequestLiveTick | OnNotifyTicksLONG | 官方明寫不會回補 |
兩個函式照官方說明要擇一使用。另外注意,RequestLiveTick 官方列的相關通知事件只有成交明細。
我們自己的實作用的是 RequestTicks,RequestLiveTick 沒有使用經驗,所以這裡只照官方寫,不做延伸推論。
群益API Tick 回補會重送嗎?程式要能承受
官方只說回補發生在「首次索取」。重新訂閱或重新連線之後會不會再回補一次,官方沒有寫,我們也沒有觀察紀錄可以回答。
但程式要能承受「同一段 Tick 又送來一次」。如果重送了、而程式沒有去重,同一筆成交會被算兩次,成交量與均價都會偏大。
所以累加類的數值(成交量、均價這類)要嘛能去重,要嘛在重新連線時清空、由回補重建。我們的實作兩種都做。
這件事跟〈群益API 報價停止更新怎麼辦〉直接相關:那篇講的自救會重新訂閱、甚至重新連線,正是需要承受重送的場景。
去重為什麼不能只記最後一筆?
兩個 Tick 事件都有一個參數 nPtr。事件本身的參數說明只寫「表示資料的位址(Key)」;但同一本官方文件在 SKQuoteLib_GetTickLONG 與 SKTICK 結構裡,對同一個欄位的說明是「第幾筆成交明細,又稱成交明細順序」「第幾筆成交明細,由0開始」。也就是說,它是第幾筆成交的順序編號。
拿它來去重很自然。但「只記最後一筆的編號,比它小就丟」有兩個破口:
- 到達順序沒有保證。 回補事件和即時事件誰先到、會不會交錯,官方沒有寫,我們也沒有觀察紀錄。如果即時的 Tick 先到、回補的後到,回補的編號都比較小,會全部被當成重複丟掉。
- 編號會往回跳。 我們在日盤切夜盤時遇到過編號重新起算。只看「有沒有變大」,新時段的 Tick 會全部被丟掉,K 棒就停了。
做法是記「這個時段收過哪些編號」,收過就丟、沒收過就收。 這樣不依賴到達順序,回補與即時交錯也不會漏。
回補與即時要共用同一份編號
這裡要特別說明,因為它看起來跟〈群益API 報價停止更新怎麼辦〉的「一條流一個時間」相反。
- 那篇記的是健康度(多久沒收到資料)。成交明細與五檔是兩條不同的資料流,共用一個時間會互相遮住,所以要分開。
- 這裡記的是去重的編號。回補與即時送的是同一串成交,同一筆成交如果從兩個事件各送一次,共用才擋得住重複計算,所以要共用。
判斷方式其實是同一個問題:這個狀態代表的是一條流,還是好幾條?
(「兩個事件的編號是同一個序列」是從官方「第幾筆成交明細」的定義推出來的,官方沒有直接這樣寫。)
怎麼知道時段切換了?
官方沒有提供時段切換的事件或旗標。我們的做法是看「編號往回跳、而且成交時間往前走了一段」來判斷。
如果你要用成交時間來判斷,注意夜盤跨過午夜時 nDate 的語意:官方寫的是「交易日期。(YYYYMMDD)」,午夜之後它是交易日還是日曆日,我們沒有紀錄,請自行確認。
日盤切夜盤要做什麼?
兩個 Tick 事件的官方備註都有這一句(以回補事件為準,節錄):
T盤切換T+1盤,不保留前一盤資料,開發者需自行清除前一盤資料。
也就是說,時段切換時,元件不保留前一盤的資料,清除的工作要你自己做。具體要清哪些,由你的程式決定;上一節「收過的編號」就是其中一項——清掉之後,新時段從 0 起算的編號才不會被誤判成重複。
注意這一句與上一節是兩件事:官方告訴你「切換時要自己清」;「切換後編號會重新起算」是我們的開發經驗,不是官方的說法。
試算揭示的 Tick 要算進去嗎?
兩個 Tick 事件的最後一個參數 nSimulate,官方說明是「0:一般揭示 1:試算揭示」,備註寫的是(節錄):
未進行揭示處理,開發人員需自行接手處理(判斷收的tick 為一般或試算揭示)。
元件不替你過濾,要自己判斷。 要不要把試算揭示算進成交量或走勢,是你的決定;下面的程式碼只示範略過的做法。
群益API Tick 回補的 7 條處理原則+程式碼
- 回補事件與即時事件分開處理:我們的做法是回補只拿來重建走勢與統計,不觸發即時判斷。
- 不需要當天回補,就改用
RequestLiveTick(官方:與RequestTicks擇一使用)。 - 去重用「這個時段收過哪些編號」:回補與即時共用一份,時段切換時清掉;不要只記最後一筆。
- 累加類數值要能承受重送:能去重,或在重新連線時清空、由回補重建。
- 日盤切夜盤時自己清掉前一盤資料(官方原文)。
- 自己判斷試算揭示(
nSimulate),決定要不要算進去。 - 回補與即時事件裡,不要呼叫
GetTickLONG、GetStockByIndexLONG。 官方回補事件的備註是(節錄):「避免在SKQuoteLib_OnNotifyHistoryTicksLONG通知事件裡進行SKQuoteLib_GetTickLONG()、SKQuoteLib_GetStockByIndexLONG(只可取得基本資料)」,即時事件也有同樣的一句。
// 每檔商品記住這個時段已經收過的編號(nPtr:第幾筆成交明細)
Dictionary<string, HashSet<int>> _seenPtr = new Dictionary<string, HashSet<int>>();
// 收過的編號就丟掉;不依賴到達順序,回補與即時交錯也不會漏
bool Accept(string stockNo, int nPtr)
{
if (!_seenPtr.TryGetValue(stockNo, out var seen))
_seenPtr[stockNo] = seen = new HashSet<int>();
return seen.Add(nPtr); // 已經有了就回 false:重複
}
// 日盤切夜盤:官方要你自己清除前一盤資料,收過的編號也一起清掉
void OnSessionSwitch() { _seenPtr.Clear(); }
// 回補:只拿來重建走勢與統計
void OnHistoryTick(string stockNo, int nPtr, int nDate, int nTimehms, int nClose, int nQty, int nSimulate)
{
if (nSimulate == 1) return; // 試算揭示:此處示意為略過,是否採用由你決定
if (Accept(stockNo, nPtr)) Rebuild(stockNo, nClose, nQty);
}
// 即時:才交給策略判斷
void OnLiveTick(string stockNo, int nPtr, int nDate, int nTimehms, int nClose, int nQty, int nSimulate)
{
if (nSimulate == 1) return;
if (Accept(stockNo, nPtr)) OnTrade(stockNo, nClose, nQty);
}幾點補充:
OnHistoryTick、OnLiveTick是示意用的處理函式,分別由OnNotifyHistoryTicksLONG、OnNotifyTicksLONG呼叫。事件本身只給索引,商品代碼要自己查;價格要除以 10 的sDecimal次方(見〈群益API 報價價格為什麼是整數〉),片段都略過了。- 如果你選擇「重新連線時清空累加值、由回補重建」,收過的編號也要一起清掉,否則重建時送來的回補會被當成重複丟掉。
- 事件在元件自己的執行緒上觸發,這份編號由兩個事件共用,實際程式要做同步,細節見〈群益API 事件回呼跑在哪個執行緒〉。
常見問題
盤中啟動,為什麼一訂閱就收到一堆舊的 Tick?
那是群益API Tick 回補。依官方說明,第一次索取個股成交明細時,OnNotifyHistoryTicksLONG 會回補當天的 Tick。它和即時的 OnNotifyTicksLONG 是兩個不同的事件。
回補和即時的 Tick 要怎麼分?
只能看是哪一個事件送來的。兩個事件的參數完全相同,光看參數分不出新舊。
不想要回補,怎麼辦?
官方另有 SKQuoteLib_RequestLiveTick,明寫不會回補歷史成交明細資料,並要求與 RequestTicks 擇一使用。
夜盤開始後 K 棒不動了,可能是什麼原因?
檢查去重邏輯。如果只記「最後一筆的編號、比它小就丟」,而時段切換後編號可能重新起算(我們遇到過),新時段的 Tick 就會全部被丟掉。改成記「這個時段收過哪些編號」,並在時段切換時清掉。
風險揭露
期貨與選擇權屬高槓桿商品,價格波動可能造成超過原始保證金的損失,交易人須自負交易責任。本文為程式開發技術教學,說明成交明細回補與即時資料的區分與去重,不構成投資建議,也不保證任何交易結果。程式化交易並不降低市場風險,反而可能因程式錯誤造成非預期的行為——把回補的舊成交當成即時成交,可能讓程式在錯誤的時間點觸發判斷。
投資人教育資源
| 機構 | 資源 |
|---|---|
| 臺灣期貨交易所(TAIFEX) | 期貨及選擇權數位學習網 |
| 證券暨期貨市場發展基金會 | 證券期貨市場教育推廣 |
| 金融智慧網 | 金管會金融知識平台 |
| 中華民國期貨業商業同業公會 | 期貨業法規與宣導 |
| 證券投資人及期貨交易人保護中心 | 投資人保護與申訴 |
延伸閱讀
- 〈群益API 報價停止更新怎麼辦〉——自救會重新訂閱或重新連線,正是需要承受回補重送的場景。
- 〈群益API 報價訂閱有哪兩層〉——
RequestTicks是深度層,事件只給索引。 - 〈群益API 報價價格為什麼是整數〉——Tick 裡的價格與時間都是整數編碼。
- 〈群益API 下單初始化有哪 7 步〉——回報通道連上後也會先回補,同一個形狀。
參考資料
RequestTicks的相關通知事件、OnNotifyHistoryTicksLONG回補說明、RequestLiveTick備註、兩個 Tick 事件的參數說明與備註(時段切換、試算揭示、事件內不呼叫的函式),依群益官方元件說明文件主手冊(V2.13.57)與「國內報價」章節。nPtr為第幾筆成交明細、由 0 開始,依官方SKQuoteLib_GetTickLONG參數說明與SKTICK結構說明。- 回補與即時分流、以收過的編號去重、重新連線時清空重建、時段切換時編號重新起算:實作與開發經驗,非官方文件記載。
免責聲明
本文章僅作為群益API實作經驗分享,不構成投資建議,且策略及程式皆應自行撰寫。期貨及衍生性金融商品交易屬高風險投資,請謹慎評估自身風險承擔能力。







