群益API Tick 回補與即時 Tick 走兩個不同事件的示意圖

群益API Tick 回補是什麼?盤中啟動收到舊成交的區分方式

盤中啟動程式,一訂閱就湧進一大批 Tick,時間全都是今天稍早的。策略被觸發了好幾次,成交量算出來也比實際大。

這不是元件出錯,是群益API Tick 回補在做它該做的事。 問題在於你的程式把回補送來的舊成交,當成了剛剛發生的。

群益API Tick 回補與即時 Tick 走兩個不同事件的示意圖

群益API Tick 回補是什麼?兩個事件,一個舊一個新

用 SKQuoteLib_RequestTicks 訂閱成交明細時,官方在「相關通知事件」欄列了三個事件:

即時Tick由OnNotifyTicksLONG事件通知。 Tick回補由 OnNotifyHistoryTicksLONG事件通知。 最佳五檔由OnNotifyBest5LONG事件通知。

而 OnNotifyHistoryTicksLONG 的官方說明是:

當首次索取個股成交明細,此事件會回補當天Tick。

所以盤中啟動、第一次訂閱某檔商品時,你會先收到當天稍早、已經成交的 Tick,而且是從另一個事件進來的。

關鍵在這裡:兩個事件的參數完全相同。 光看參數分不出哪一筆是舊的、哪一筆是新的,只能靠「是哪一個事件送來的」來分。 如果兩個事件接到同一個處理函式,當天稍早的成交就會被當成剛剛發生的。

我們的做法是分流:回補的 Tick 只拿來重建走勢與統計,不觸發即時的策略判斷。這是我們的選擇,你也可以有別的用法——重點是兩個事件要分得開。

如果你讀過〈群益API 下單初始化有哪 7 步〉會覺得眼熟:回報通道連上之後也會先回補。連上線就先給你一批舊資料,是這套 API 反覆出現的行為。

不需要回補,可以改用 RequestLiveTick 嗎?

可以。官方另有一個訂閱函式 SKQuoteLib_RequestLiveTick,備註寫的是(節錄):

與SKQuoteLib_RequestTicks不同,不會回補歷史成交明細資料,請擇一使用

函式官方列的相關通知事件回補
SKQuoteLib_RequestTicksOnNotifyTicksLONG、OnNotifyHistoryTicksLONG、OnNotifyBest5LONG首次索取時回補當天 Tick
SKQuoteLib_RequestLiveTickOnNotifyTicksLONG官方明寫不會回補

兩個函式照官方說明要擇一使用。另外注意,RequestLiveTick 官方列的相關通知事件只有成交明細。

我們自己的實作用的是 RequestTicks,RequestLiveTick 沒有使用經驗,所以這裡只照官方寫,不做延伸推論。

群益API Tick 回補會重送嗎?程式要能承受

官方只說回補發生在「首次索取」。重新訂閱或重新連線之後會不會再回補一次,官方沒有寫,我們也沒有觀察紀錄可以回答。

但程式要能承受「同一段 Tick 又送來一次」。如果重送了、而程式沒有去重,同一筆成交會被算兩次,成交量與均價都會偏大。

所以累加類的數值(成交量、均價這類)要嘛能去重,要嘛在重新連線時清空、由回補重建。我們的實作兩種都做。

這件事跟〈群益API 報價停止更新怎麼辦〉直接相關:那篇講的自救會重新訂閱、甚至重新連線,正是需要承受重送的場景。

去重為什麼不能只記最後一筆?

兩個 Tick 事件都有一個參數 nPtr。事件本身的參數說明只寫「表示資料的位址(Key)」;但同一本官方文件在 SKQuoteLib_GetTickLONG 與 SKTICK 結構裡,對同一個欄位的說明是「第幾筆成交明細,又稱成交明細順序」「第幾筆成交明細,由0開始」。也就是說,它是第幾筆成交的順序編號。

拿它來去重很自然。但「只記最後一筆的編號,比它小就丟」有兩個破口:

  1. 到達順序沒有保證。 回補事件和即時事件誰先到、會不會交錯,官方沒有寫,我們也沒有觀察紀錄。如果即時的 Tick 先到、回補的後到,回補的編號都比較小,會全部被當成重複丟掉。
  2. 編號會往回跳。 我們在日盤切夜盤時遇到過編號重新起算。只看「有沒有變大」,新時段的 Tick 會全部被丟掉,K 棒就停了。

做法是記「這個時段收過哪些編號」,收過就丟、沒收過就收。 這樣不依賴到達順序,回補與即時交錯也不會漏。

回補與即時要共用同一份編號

這裡要特別說明,因為它看起來跟〈群益API 報價停止更新怎麼辦〉的「一條流一個時間」相反。

  • 那篇記的是健康度(多久沒收到資料)。成交明細與五檔是兩條不同的資料流,共用一個時間會互相遮住,所以要分開。
  • 這裡記的是去重的編號。回補與即時送的是同一串成交,同一筆成交如果從兩個事件各送一次,共用才擋得住重複計算,所以要共用。

判斷方式其實是同一個問題:這個狀態代表的是一條流,還是好幾條?

(「兩個事件的編號是同一個序列」是從官方「第幾筆成交明細」的定義推出來的,官方沒有直接這樣寫。)

怎麼知道時段切換了?

官方沒有提供時段切換的事件或旗標。我們的做法是看「編號往回跳、而且成交時間往前走了一段」來判斷。

如果你要用成交時間來判斷,注意夜盤跨過午夜時 nDate 的語意:官方寫的是「交易日期。(YYYYMMDD)」,午夜之後它是交易日還是日曆日,我們沒有紀錄,請自行確認。

日盤切夜盤要做什麼?

兩個 Tick 事件的官方備註都有這一句(以回補事件為準,節錄):

T盤切換T+1盤,不保留前一盤資料,開發者需自行清除前一盤資料。

也就是說,時段切換時,元件不保留前一盤的資料,清除的工作要你自己做。具體要清哪些,由你的程式決定;上一節「收過的編號」就是其中一項——清掉之後,新時段從 0 起算的編號才不會被誤判成重複。

注意這一句與上一節是兩件事:官方告訴你「切換時要自己清」;「切換後編號會重新起算」是我們的開發經驗,不是官方的說法。

試算揭示的 Tick 要算進去嗎?

兩個 Tick 事件的最後一個參數 nSimulate,官方說明是「0:一般揭示 1:試算揭示」,備註寫的是(節錄):

未進行揭示處理,開發人員需自行接手處理(判斷收的tick 為一般或試算揭示)。

元件不替你過濾,要自己判斷。 要不要把試算揭示算進成交量或走勢,是你的決定;下面的程式碼只示範略過的做法。

群益API Tick 回補的 7 條處理原則+程式碼

  1. 回補事件與即時事件分開處理:我們的做法是回補只拿來重建走勢與統計,不觸發即時判斷。
  2. 不需要當天回補,就改用 RequestLiveTick(官方:與 RequestTicks 擇一使用)。
  3. 去重用「這個時段收過哪些編號」:回補與即時共用一份,時段切換時清掉;不要只記最後一筆。
  4. 累加類數值要能承受重送:能去重,或在重新連線時清空、由回補重建。
  5. 日盤切夜盤時自己清掉前一盤資料(官方原文)。
  6. 自己判斷試算揭示(nSimulate),決定要不要算進去。
  7. 回補與即時事件裡,不要呼叫 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)期貨及選擇權數位學習網
證券暨期貨市場發展基金會證券期貨市場教育推廣
金融智慧網金管會金融知識平台
中華民國期貨業商業同業公會期貨業法規與宣導
證券投資人及期貨交易人保護中心投資人保護與申訴

延伸閱讀

參考資料

  • RequestTicks 的相關通知事件、OnNotifyHistoryTicksLONG 回補說明、RequestLiveTick 備註、兩個 Tick 事件的參數說明與備註(時段切換、試算揭示、事件內不呼叫的函式),依群益官方元件說明文件主手冊(V2.13.57)與「國內報價」章節。
  • nPtr 為第幾筆成交明細、由 0 開始,依官方 SKQuoteLib_GetTickLONG 參數說明與 SKTICK 結構說明。
  • 回補與即時分流、以收過的編號去重、重新連線時清空重建、時段切換時編號重新起算:實作與開發經驗,非官方文件記載。

免責聲明

本文章僅作為群益API實作經驗分享,不構成投資建議,且策略及程式皆應自行撰寫。期貨及衍生性金融商品交易屬高風險投資,請謹慎評估自身風險承擔能力。

延伸閱讀|相關文章

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *