← 所有文章

工程 · 2026.10.06 · 4 min · 更新於 2026.10.26

對帳告警響了兩次:Bitfinex 放貸部位換邊造成的漏算與重複計息

我用實際付息除以帳面應計的比值監控收益有沒有算錯,這個告警響過兩次,一次漏算、一次重複計息,根因都是 Bitfinex 把同一筆部位分在 credits 與 loans 兩側。

閱讀設定
主題
字級
寬度

對帳告警響過兩次,一次比值 0.970、分母漏算,一次 0.488、同一段利息算了兩次。

這個比值是「實際付息 ÷ 帳面應計」的 7 天比值,我的 Bitfinex 自動放貸平台用它檢查使用者看到的收益有沒有算錯。

兩次方向相反,根因都一樣:Bitfinex 把同一筆放貸部位分在 credits 與 loans 兩側,換邊時換一個新 id、卻保留原本的開倉時間。

兩次自動分診的推論都不對,真正找到原因的是把數字對到收斂。

對帳比值怎麼算

「放貸中」的真值是 Bitfinex 回來的部位快照,不是 bot 自己的訂單表。

訂單表是 bot 的動作紀錄,取消、部分成交、手動操作都可能留下殘留的 row,修補每一種殘留只是在維護一份次級投影。

Bitfinex 每天付一次利息,付進來的已經扣掉 15% 手續費。我這邊根據部位快照,把每個部位的 金額 × 利率 × 秒數 按秒攤到每一天,算出帳面應計(照利率和時間算出、扣手續費前的利息),兩者相除:

funding_recon_ratio = 7 天內實際付息總和 / 7 天內帳面應計總和

付息扣過手續費,所以乾淨的帳戶應該落在 0.85 附近。

比值連續 3 天跑出帶狀區間就告警:太低是應計算多了,太高是有部位漏掉。

這個比值跟金額大小無關,每個帳戶、每個幣別各自一條,哪個帳戶開始偏就知道從哪查。

同一筆部位有兩個家

Bitfinex 的放貸部位分 /funding/credits 和 /funding/loans 兩種 endpoint。借方還沒動用的錢在 loans 那一側,開始動用之後移到 credits 那一側。

兩邊的 id 是不同的命名空間,換邊時拿到新 id,但保留開倉時間 MTS_OPENING,金額也不變。

已經結束的部位會進各自的 hist(歷史紀錄)。credits 的 hist 會寫進我的 credit 歷史表,loans 的不會,因為 id 命名空間不同,混進去會污染帳本。

Bitfinex 120 天放貸的 bid-take lane上線後,它的成交大多落在 loans 那一側。

接下來兩個 bug 都跟這一側有關。

結構圖:同一筆放貸部位分在 loans 與 credits 兩側,換邊時拿到新 id,開倉時間 MTS_OPENING 和金額不變;120 天 bid-take 的成交大多落在 loans 側。部位結束後各自進 hist,credits 的 hist 寫進 credit 歷史表,loans 的 hist 因為 id 命名空間不同而不寫進去。

第一次 0.970:平倉部位從帳上消失

bid-take 上線約十天後,告警對正式站的一個帳戶響了三天,比值 0.970。

Claude Code 唯讀自動分診指向「每日物化 job 對新 lane 的 pro-rata 算錯」,方向對了一半,機制是錯的。物化是把算好的結果先存成表,pro-rata 是按比例分攤。

部位快照是活部位的鏡像,部位離開交易所就刪列。credits 那一側之後會從 hist 進歷史表,loans 那一側不會。

所以一筆 bid-take 部位平倉的瞬間,它整段生命週期的應計就從帳面上消失,分母被低估,比值被撐高。

視窗內剛好有五筆這樣的部位。我把它們按秒補回去,比值從 0.970 變成 0.851。

差額剛好是這五筆,所以不用再找第二個原因。對帳類的 bug,先把數字對到收斂再動手。

修法是加一張平倉快照表:刪掉離開的部位之前,先把金額、利率、開倉與平倉時間留下來,每日物化 job 把它當第三個來源讀。

唯一要想清楚的是換邊不能算平倉。

部位從 loans 移到 credits,舊 id 消失、新 id 出現,看起來像一筆平倉加一筆新開倉,寫進平倉表就會跟新的那筆重疊。所以同一輪裡看到一去一來、開倉時間與金額相同,就認成換邊,不寫平倉紀錄。

我也考慮過讓物化 job 記住算過的應計、只增不減,但那會把之後真正的修正也擋掉,等於把看得到的錯換成看不到的錯。

第二次 0.488:換邊那段算了兩次

兩週後告警又響了,這次是另一個帳戶,比值 0.488,分母多算了。

分診的推論是「使用者暫停了放貸,快照凍住了」,但兩個 config 都是 active,推論不成立。

我把那個帳戶的每日應計逐日攤開。從某一天起,開著的部位數從 1 變 2,應計剛好翻倍。

再把歷史表與平倉快照裡開倉時間、金額相同的列對起來。一筆 120 天的部位從 credits 換到 loans,舊 credit 的 hist 涵蓋開倉到換邊這段,新的 loan 又從開倉時間算起,重疊的那幾天每天都算了兩次。

換邊也不罕見,近 60 天的 6,528 筆 hist credit 裡,有 1,452 筆是換邊來的。

反方向也有問題。上一次加的換邊偵測,會讓一個新 id 把所有同 key 的舊 id 都吃掉。同一批成交的等額部位常常一起換邊,結果只有第一筆被認成換邊,其餘被記成平倉。

Bitfinex 本來就告訴我們部位是從哪個 endpoint 來的,只是我沒存。

修法是在兩張快照表加一個 from_loans 欄位。物化 job 對 loans 那一側的列,去找開倉時間與金額相同的 hist credit,只從那筆結束之後才開始計息;有一筆還沒結束,整段就不算。

這一條規則兩個方向都對。換邊偵測也改成一個新 id 只配一個舊 id。

示意圖:舊 credit id 的 hist 涵蓋開倉到換邊;修正前新的 loan id 從開倉時間開始計息,開倉到換邊這段和舊 credit 重疊、每天算兩次;修正後新的 loan 只從那筆 hist credit 結束後才計息。

重算之後,三個受影響帳戶的 7 天比值:

帳戶 重算前 重算後
A 0.734 0.855
B 0.488 0.914
C 修前沒量 0.877

正式站回填出的 loans 平倉列,沒有一筆跟 hist credit 撞 key,所以正式站沒有中這個 bug。

分診只能當線索

第一次分診猜對了哪條 lane、猜錯了哪裡算錯,第二次連哪裡壞都猜錯,照著改都會改到沒壞的地方。

MTS_OPENING 是經濟上的開倉時間,不是這個 id 的出生時間。

用開倉時間當計息起點、又把多個 id 加總的地方,都要想同一筆部位會不會以不同 id 出現兩次。

交易所已經告訴你的資訊,第一次就存下來。之後要回推,只能靠時間與金額猜。

#系統設計#除錯#Bitfinex#對帳

留言

載入留言中…

下一篇

工程

Claude Code 自動分診 Sentry 告警:用 --allowedTools 限定唯讀的設計與踩坑

告警響了先讓只有唯讀工具的 Claude session 查根因、把結論貼到 Discord,這篇講權限邊界怎麼畫,以及模型改寫指令路徑、Sentry 噪音這兩個踩過的坑。