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}),
欄位是 tokens 跟 last_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
@register_strategy
不帶 throttle_tag,guard 會自動把它丟進 default 桶,bucket key 用 strategy
類別名 fallback。幾個比較容易踩雷的地方
- Backtest mode 下 throttle 是 in-memory。 Backtest engine 會把 SQLite 換成 process 內 dict,所以同一份策略在回測時看到的「拒單分佈」跟 live 不會 100% 對齊; live 是跨 process 共享桶、backtest 是每個 worker 自己一桶。
- burst ≠ rate。
burst是桶容量;一支策略整夜沒下單, 早上開盤瞬間有可能一次擠出burst那麼多單 — 就算rate設得很保守。 如果你不要這個爆發行為,burst要刻意壓到接近rate。 - OCO 兩腿算兩格。 一組 OCO 觸發進場 + 停損掛單時,guard 看到的是兩筆獨立 order, 所以會扣兩格 token,不是一格。寫策略時要把這個算進 sizing。
常見問題
- 內部健診打單怎麼跳過節流?
- 呼叫端在
OrderContext裡塞internal_probe=True,guard 會比對 process namespace 是不是quanta.healthcheck,是的話完全略過 bucket(連 log 都不寫)。 - 哪邊看誰被擋最多?
- 每次
BLOCKED_BY_THROTTLE都會打一筆 metricthrottle.blocked, tag 帶 strategy 名稱跟 bucket key 類型。Grafana Trading → Health 那塊有現成 panel, 分鐘級彙總,按 strategy 排序。 - 能不能讓某個帳戶限額更高?
- 可以,在 yaml 下面加
overrides:段,列帳戶 ID 跟新閾值。Overrides 是 hot reload, 不用重啟 daemon — 但 reload 是每 30 秒 poll 一次,不要期待秒級生效。