研究筆記 · 機制拆解

quanta/exec 的下單節流保護怎麼運作

一句話結論 — 每一筆 place_order() 在送進 broker adapter 之前都會先過 throttle_guard(),這個 guard 會把呼叫者收斂成一個 限流桶 key、從共用 KV 拿出對應的 token bucket,扣一格 token 或直接擋成 BLOCKED_BY_THROTTLE。每支策略的閾值寫在 config/throttle_rules.yaml,沒寫的策略一律落到 default 等級 (每分鐘 30 單、burst 45)。

下單一路上發生的事

展開每一步看實際跑的程式碼跟所在位置。整條 path 大約 50 行,每筆下單多 0.6 ms p50 — 跟下單本身的網路延遲比可以忽略。

1 · 把呼叫者收斂成 bucket key guard/throttle.py:18

Guard 第一件事就是把 order 縮成一個 bucket_key:如果這筆單帶 strategy_id,就用 strategy 當 key;沒有的話用 account_id。手動下單(CLI 觸發)會被歸到一個獨立的 manual 桶, 上限刻意調低,避免人手抖出 20 單砸到自己的策略。

2 · 找出對應的 token bucket guard/bucket_core.py:7

策略名稱加上 bucket key 對應到一個 SQLite row(key 形式 tb:{strategy}:{bucket_key}), 欄位是 tokenslast_refill_ts。第一次出現的 key 直接以滿桶建立 — 策略剛啟動的時候不會被自己的 cold start 卡住。

3 · 補 token 再扣一格 guard/bucket_core.py:34

補注量是經過時間 × refill rate(上限 burst),然後扣掉 1 格。整段 read-modify-write 包在一個 SQLite transaction + immediate lock 裡,避免兩條 strategy thread 同時下單把 token 透支。

4 · token 不夠就拒單 guard/throttle.py:52

如果扣完 tokens < 0,guard 不會送單到 broker,直接 raise ThrottleBlocked 並附上 cooldown_ms(再過幾毫秒會補到 1 格)。 被擋的單會寫進 throttle_block.log 而不是進 order book — 這條 log 是後續歸因 「為什麼那根 BAR 沒進場」的第一手資料。

幫一支策略設定自己的閾值

不用動 guard 本體。在 config/throttle_rules.yaml 加一段以 strategy 名稱為 key,再 (可選)把 strategy 的 throttle_tag 設成這個名稱讓 guard 對得起來就好。

# config/throttle_rules.yaml
default:
  rate: 30/min
  burst: 45

v2w_micro_tx:
  rate: 8/min
  burst: 12
  key: strategy_id   # 也可以填 account_id
# strategies/v2w_micro_tx.py
@register_strategy(
    name="v2w_micro_tx",
    throttle_tag="v2w_micro_tx",
)
class V2WMicroTx(BaseStrategy):
    def on_bar(self, bar):
        ...
2026-05-14 09:31:18.244  WARN  throttle.blocked
  strategy = v2w_micro_tx
  bucket   = strategy:v2w_micro_tx
  remaining_tokens = 0
  cooldown_ms = 4180
  reason   = BURST_DRAINED
如果你的策略走 default 就夠,連 YAML 都不用加。只要 @register_strategy 不帶 throttle_tag,guard 會自動把它丟進 default 桶,bucket key 用 strategy 類別名 fallback。

幾個比較容易踩雷的地方

常見問題

內部健診打單怎麼跳過節流?
呼叫端在 OrderContext 裡塞 internal_probe=True,guard 會比對 process namespace 是不是 quanta.healthcheck,是的話完全略過 bucket(連 log 都不寫)。
哪邊看誰被擋最多?
每次 BLOCKED_BY_THROTTLE 都會打一筆 metric throttle.blocked, tag 帶 strategy 名稱跟 bucket key 類型。Grafana Trading → Health 那塊有現成 panel, 分鐘級彙總,按 strategy 排序。
能不能讓某個帳戶限額更高?
可以,在 yaml 下面加 overrides: 段,列帳戶 ID 跟新閾值。Overrides 是 hot reload, 不用重啟 daemon — 但 reload 是每 30 秒 poll 一次,不要期待秒級生效。