← 回到筆記
可重現性

這份研究是怎麼搭起來的

足以手算核對筆記裡的任一數字,或用 Binance 的公開資料重建一份等價研究。所有輸入都是免費且公開的。

資料

三個公開來源,全部取自 Binance 的公開歸檔與其 metrics 端點,涵蓋 USD-M 永續合約。沒有付費資料,也沒有專有資料。

輸入頻率用途
K 線1 分鐘價格、報酬、波動度、成交量
溢價指數1 分鐘基差類特徵
已實現資金費逐事件只在其結算那一根上計入現金流
未平倉量1 小時持倉結構類特徵

四項輸入都公開且免費。資金費是現金流,不是解釋變數:讓模型看見自己的資金費,它可能學會繞著結算時點下單。

歸檔本身有兩處怪癖必須處理,否則面板會無聲地錯位。K 線檔案在歷史中途換過表頭慣例,而某一段早期資料的時間戳是微秒而非毫秒。兩者都在讀檔時歸一。

未平倉量的覆蓋範圍是硬上限,而不是抓取上的限制。平台只從 2021 年 12 月起發布規模第二大合約的 metrics,更早的日期對多數合約一律回傳空值。針對全部 354 個合約做的回溯補齊跑了 148 分鐘,新增零列。這也是為什麼特徵比較只落在較晚的窗格上。

標的池

354 個合約,其中 56 個在樣本結束前已下市,並保留在它們真正有成交的日期上。挑選是時點式的:每個月的交界處,依嚴格在此之前 30 天的日均美元成交量排序,前 40 名取得資格。

資格條件在排序之前就套用,因此不可能依賴任何結果。計價貨幣必須是 USDT,有到期日的合約排除,非加密資產的標的也排除。

# history eligibility is tested on genuinely traded volume, not file existence,
# because the archive pads a contract's file after its last real trade
traded = bars.filter(pl.col("quote_volume") > 0)
last_real = traded["open_time"].max()
bars = bars.filter(pl.col("open_time") <= last_real)

觀測與動作

每小時做一次決策。動作是 20 個介於負一與正一之間的數字,每個欄位一個,讀作要在該合約上持有的帳戶比重。正值是做多,負值是做空;因為是永續合約,做空是允許的。

接著依序套用四條規則。任何落在正負百分之二以內的值被歸成精確的零,好讓「空手」是一個真的可達的選擇,而不是一個不穩定的平衡點。單一合約上限為帳戶的四分之一。若權重的絕對值之和超過一,整組等比縮回:因此沒有槓桿。持有的合約在該小時沒有交易時,該欄位被強制歸零。

觀測內容包含每個合約自己的特徵欄位,加上三個屬於代理自身的量:它目前持有的權重、這個部位目前是賺還是賠、以及已經持有多久。接著是十二個帳戶層級的量:總曝險與淨曝險、距高點的回落、權益的對數、活躍與持有的合約數、在回合中的進度、已實現損益、平均成交規模、累計成本與資金費,以及總曝險上限。

若總曝險超過上限的一點五倍,部位會被強制以市價縮回;若權益跌到起始值的四分之一以下,該回合結束。

成本與資金費

成本寫在獎勵裡面,而不是事後再扣掉。這一點很要緊:否則那兩個逐筆交易的獎勵會對成本完全無感,而一個對成本無感的逐筆獎勵,已被證實會教出過度交易。

fee        = taker_fee * traded_notional          # 5 bp per fill
slippage   = (base_slip + impact_k * sigma * sqrt(participation)) * traded_notional
participation = traded_notional / max(interval_dollar_volume, 1.0)   # capped at 5%
funding    = position_notional * funding_rate     # on the true settlement bar only

衝擊項是以該合約的已實現波動度縮放,而不只是參與率。少了波動度項,這個式子在量綱上就是錯的:早先一版曾對全池最大合約上的一筆一萬美元交易收取 2.3% 的滑價。

參與率是對一個固定的帳戶規模衡量,而不是對權益的比例,否則那個上限永遠咬不到。低於帳戶百萬分之一的交易會被取消,這避免單精度的進位雜訊被記成每次執行約一千筆的幽靈交易。

參數數值
吃單手續費每筆成交 5 個基點
基礎滑價每筆成交 2 個基點
市場衝擊參與率的平方根,以已實現波動度縮放
參與率上限該區間美元成交量的 5%
資金費已實現費率,只在其事件那一根上計一次
帳戶規模100,000 美元,固定

成本參數。每一筆成交都照部位的實際規模收取。

滾動驗證

二十個滾動窗格。每個窗格取 18 個月訓練、3 個月驗證、3 個月測試,每道邊界都有 7 天的淨化與 1 天的禁運。淨化長度取自任一特徵所需的最長回看期,因此沒有一列訓練資料能看見與測試目標重疊的任何一根。

標準化只在訓練列上擬合。檢查點依訓練期四個切片上的驗證權益挑選,絕不依測試挑選。每個設定只碰測試一次。

篩選階段用了第 1、3、5、8、11、14、17、19 號窗格。樣本外期間為 2021 年 11 月 17 日至 2026 年 8 月 17 日。

# features from completed bar t -> decision after bar t -> fill at bar t+1
# a rotation into a slot whose contract changed must NOT settle against the
# previous contract's price, which is the single worst defect found in build
rotated = (sym_id[t+1, k] != sym_id[t, k]) or (not live[t+1, k])
step_ret = 0.0 if rotated else (px[t+1, k] / px[t, k] - 1.0)

獎勵設計

六種設計,其餘條件一致。兩種只在部位平倉時才發話,四種每小時都發話。

設計獎勵什麼何時發放
逐筆淨損益扣掉該筆自身成本後的已實現損益平倉時
逐筆勝負符號賺錢平倉給正一,賠錢平倉給負一平倉時
對數報酬帳戶價值的每小時變化每小時
報酬減最大回落同上,再扣一筆對距高點深度的懲罰每小時
直接給夏普持續更新的差分夏普比率每小時
波動度縮放報酬每小時報酬除以近期波動度每小時

六種設計。前兩種對應原始規格書,並且是在扣掉該筆交易自己的手續費、滑價與資金費之後結算的。

對照代理

七個,在同一個環境與同一套成本模型上執行。空手、買進並持有整個標的池、買進並持有單一合約、等權重、一條順勢規則、一條均值回歸規則,以及一個隨機代理。

真正關鍵的是隨機代理。它與被比較的那個策略的實測換手率對齊:隨機持有一組部位,持有的步數正好複製出同樣的交易強度。拿一個策略去比買進並持有,是在偏袒那個策略,因為它付的成本多得多。此外也產生一組不同持有期的隨機代理階梯,讓每個策略都能對上最接近自己換手率的那一階。

空手這個代理即使從不交易也保留。在這裡什麼都不做是一個正當的策略,若因為沒有交易就把它取消資格,就等於拆掉那個唯一能偵測「代理學會了不進場」的對照。

統計推論

登錄的統計量是彙總樣本外風險調整後報酬在各個隨機起點上的第 25 百分位,而不是中位數。以中位數排序,會讓一個靠五個起點中三個幸運起點撐起來的結果被當成優勢呈現;這件事在本研究中確實發生過一次,並被這條規則攔下。

任何多重比較校正的試驗數,都是不同設定的個數,絕不是執行次數。同一設定的不同起點不是獨立試驗。彙總後的樣本外序列直接計分;逐窗格的數字絕不被平均成一個主要數字,窗格數也絕不乘進策略計數裡。

回測過度配適機率以對稱的組合式交叉驗證在整個設定家族上計算。家族錯誤率用 Holm 與 Benjamini-Hochberg 控制。緊縮夏普比率以不同設定的個數作為試驗數。

# per-window fractions each restart at 1.0, so they must not be summed across
# windows. The MEAN across windows is additive and the parts reconcile; a
# median of each column separately does not sum to the median net.
gross = mean(w.total + w.cost - w.funding for w in windows)
cost  = mean(w.cost for w in windows)
net   = mean(w.total for w in windows)      # gross - cost + funding == net

攔阻檢查

這些檢查在任何結果被讀取之前執行,而且失敗會中止研究,不會變成一條腳註。

檢查它攔下什麼
截斷不變性會前視的特徵,做法是要求截斷後的歷史重現完整歷史
植入目標就算刻意注入洩漏也偵測不到的機具
核心保真度快速模擬器相對一份獨立慢速實作的偏移
帳務恆等式十七項檢核,其中包含一個手算的等權重指數
樣本同一性同一個比較的各分支其實建立在不同的資料列上
退化偵測一次執行其實只是掛著策略名字的對照代理
保留區完整性有任何一次執行碰到了保留窗格
可重現性重跑一次無法精確重現其登錄的數字

每項檢查都在任何結果被讀取之前執行,失敗即中止研究。一個不可能失敗的測試不算檢查:所以植入目標那一項必須明顯變好,若沒有變好,壞掉的就是這整套機具本身。

帳務檢查是最能證明自己價值的一項。它攔下了一個用來結算「換入某欄位、而該欄位合約已經換了」的虛構價格;那個錯誤原本要宣告 31 倍的報酬,而手算出來的真相是 1.46 倍。

執行次數與偏離

219 次執行,零失敗。獎勵比較在每種設計五個隨機起點上是平衡的;其中一種設計後來為了成本實驗加到四十個起點,這段延伸是分開呈現的,而不是混進一張五起點的表裡。

有三處偏離計畫,都已記錄。二十個保留窗格裡有兩個在篩選階段被用掉,因此保留區只剩兩個未被碰過的窗格。特徵比較在發現未平倉量的覆蓋範圍會讓各分支落在不同樣本上之後,立即改用一組較晚的窗格重跑。還有一個次要的決策頻率網格被放棄,因為它相當於在四小時的預算裡要跑約 26 小時;因此決策頻率這個問題只由對照代理回答,而那是比一個策略網格更弱的證據。

有一個已發布的數字事後被更正。成本拆解的第一版同時跨窗格與跨起點加總交易明細的欄位,這既重複計算,又把各自從一重新起算的資本比例加在一起。那份明細是先進先出、且只記已實現的:它的總和與總報酬之間本來就會差一段,差的正是窗格結束時仍未平倉的庫存的市值。現在拆解一律取自模擬器的累加器。

← 回到筆記