← 回到筆記
可重現性

筆記中的每一個數字,以及它的來源

足夠讓你手動核對算式,或利用公開的Binance資料重建一項等價的研究。

交易所、樣本範圍與日期

交易所是Binance以USDT計價保證金的永續合約,樣本範圍的選取日期為2022年12月31日。合格合約必須以USDT報價與計價保證金,在該日之前已累積至少24個月的真實成交量歷史,在2022年10月至12月間有交易,且不與另一份合約的曝險重複。合格合約再依2022年10月至12月的每日成交金額中位數排序,只使用選取日之前可得的資訊,前六名被凍結下來:BTCUSDTETHUSDTDOGEUSDTXRPUSDTBNBUSDTSOLUSDT

檔案存在不等於有在交易。合格性與歷史結束時點都以正的成交量為準,合約最後一筆真實交易之後由交易所補齊的資料一律丟棄。

主要樣本外期間從2023年1月1日到2026年6月30日:十四個完整、互不重疊的三個月窗口。2026年7月因為是不完整的季度而排除。

資料與日曆

六份合約都使用從各自Binance期貨上市起到2026年6月30日的UTC小時K棒,並保留官方一分鐘K線以還原每個小時內高點與低點出現的先後順序,資金費率事件與小時級標記價格開盤價也都保留在真實時間戳上。觀察到的上市時間為BTC 2019年9月8日17:00 UTC、ETH 2019年11月27日07:00、XRP 2020年1月6日08:00、BNB 2020年2月10日08:00、DOGE 2020年7月10日09:00與SOL 2020年9月14日07:00。

面板保留共同的日曆邊界,而不是使用交集遮罩,所以某一個市場的缺口不會從另外五個市場刪掉一個小時,價格也絕不會跨過缺失的K棒向前填補。一個訊號只會為緊接著的下一個UTC小時建立委託,若該小時不存在,委託會被取消而不是往後遞延。跨越缺口的既有部位維持不變,指標則必須重新完成完整的暖身期,才能再發出下一個訊號。

只有在來源列明顯損毀或稀疏時,才可以用一分鐘資料的彙總或當下的REST回應取代某一列小時資料,而且每一次取代都會把來源回應、原因、時間戳與雜湊值寫進修復清單。在資料凍結之前,依此規則修復了十四列高低點與240列缺失的小時資料,留下333,424列小時資料、沒有缺口的面板。

六個策略家族

每一套規則都在完成的K棒t之後計算一個目標狀態,而訊號事件只有在目標狀態非零、且前一個狀態為中性或反向時才會觸發,因此一個持續存在的條件不會每小時都發出訊號。

# a desired state becomes a signal only on a fresh transition
state = np.where(fast_ema - slow_ema >  threshold * atr,  1,
        np.where(fast_ema - slow_ema < -threshold * atr, -1, 0))
prior = np.roll(state, 1)
signal = (state != 0) & ((prior == 0) | (prior == -state))
家族進場規則數值範圍
EMA趨勢快線EMA高出慢線EMA一個以ATR為單位的門檻快線12/24/48,慢線48/96/168/336,門檻0/0.25/0.50 ATR
Donchian突破收盤突破前一段滾動通道,不含當根K棒回看24/48/96/168/336/720小時,緩衝0/5/10 bp
時間序列動能L小時對數報酬的正負號超過一個以波動度縮放的門檻回看24/72/168/336/720,門檻0/0.25/0.50/1.00
RSI反轉Wilder RSI超出一組上下界後反向操作長度6/14/28/56,界線20/80, 30/70, 40/60
Bollinger反向收盤落在通道之外後反向操作回看24/48/96/168,寬度1.0/1.5/2.0/2.5個標準差
波動壓縮短期波動度低於長期波動度某個比例時發生的通道突破短期12/24,長期72/168/336,比例0.40/0.60/0.80,突破12/24/48

壓縮規則的定義性質在任何樣本外曝險之前就已經量測過:它合格的訊號事件必須真的在2021-2022的校準期間、於每一個市場都發生過,因為一個從來不過濾東西的過濾器只是一個標籤,不是一套策略。

出場、執行與K棒內路徑

六個家族只在進場邏輯上不同,共用同一套出場架構,這樣搜尋出場方式時就不會偷渡出新的家族。進場是在完成的K棒t出現訊號後,於t+1小時的開盤價成交,每個分倉同時只持有一個部位,已持倉時同方向訊號忽略,反向訊號則在下一根開盤反手並支付兩次成交。其餘情況下,部位在停損、目標或最長持有三者中最早到達的那一個離場。

進場當下已知的Wilder ATR(14)固定了整筆交易存續期間的障礙距離,而出場的數值範圍是所有家族共用的:停損為進場ATR的1.0、1.5、2.0或3.0倍,報酬風險比為1.0、1.5、2.0或3.0,最長持有為12、24、48、96或168小時。

當兩個障礙落在同一根K棒內時,小時OHLC無法說出哪一個先成交,因此引擎用一分鐘K棒把每個小時的路徑還原成開盤、第一個記錄到的極值、第二個記錄到的極值、收盤。如果兩個極值落在同一分鐘、順序無法判定,就以停損先成交處理。跳空穿過停損時以第一個可得的開盤價成交,跳空穿過目標時則不會拿到比目標更好的價格。

# events sharing one hourly timestamp resolve in a fixed order
for hour in window:
    apply_funding(hour)          # carried into this timestamp
    apply_gap_barriers(hour)
    apply_reversal(hour)         # closes old, opens new, two taker fills
    apply_max_hold(hour)
    apply_entry(hour)
    apply_intrabar_barriers(hour, minute_path[hour])

每個家族在每一個策略x市場x窗口中都恰好取得64組嘗試設定。一個以20260809為種子的確定性離散拉丁超立方產生器對訊號與出場範圍取樣,剔除重複或無效的組合,並為每個家族寫出一份標準的64列清單。這些清單只產生一次,在每個市場與每個窗口重複使用,在任何樣本外曝險之前就以標準格式序列化並計算雜湊值,而搜尋清單宣告n_searched = 2304,也就是36個家族x市場格子各64次嘗試。

一次嘗試必須在至少六個不同的日曆週內完成至少24筆交易,才有資格贏得參數區塊。失敗、非有限與不合格的嘗試仍留在分數表中,並且照樣計入64次。參數平手時以較低週轉率決勝,再以較低的標準索引決勝;選擇者平手時以較低的軸向區塊總週轉率決勝,再依凍結的市場或策略順序決定。

Walk-forward排程

每一個外層窗口都是十二個月的樣本內歷史,接著三個月的樣本外,每次前進三個月。前九個月擬合每個格子的數值參數,最後三個月原封不動地重播選定的設定,而且只有這些前推保留的分數會決定市場軸與策略軸。

for window in windows:                       # 14 non-overlapping OOS quarters
    fit  = window.is_slice[:9_months]        # choose numeric parameters per cell
    axis = window.is_slice[9_months:]        # replay the chosen config, untouched
    picks = {
        "market":   best_mean_over(axis, group="market"),    # 6 strategies on it
        "strategy": best_mean_over(axis, group="family"),    # on 6 markets
    }
    apply_once(picks, window.oos_slice)      # frozen configs, frozen decisions

持有區間跨越區塊邊界的交易,會從較早那個區塊的分數中排除,而且沒有任何標籤、出場或現金流會從參數區塊跨進軸向選擇區塊,或從樣本內跨進樣本外。指標暖身可以讀取區塊開始之前的K棒,因為那些K棒本來就已經是已知資訊,但暖身列絕不會對被評分的區塊貢獻損益。選定的設定在軸向決策之後,不會再用完整的十二個月重新擬合。

成本、資金費率與帳務

每一次進場、反手腿、停損、目標與時間出場都是taker成交,按絕對成交名目金額支付5個基點手續費與2個基點滑價,部位數量在整筆交易存續期間固定為分倉資金除以進場當下的原始中價。資金費率使用Binance觀察到的事件與其真實時間戳,正的資金費率向多方扣款、向空方入帳,並以結算時間戳之前當下持有的部位計算。

# a buy fills adversely, and the gap is booked as slippage rather than hidden in gross
fill = raw_mid * (1 + 0.0002) if side > 0 else raw_mid * (1 - 0.0002)
fee = 0.0005 * abs(quantity * fill)
slippage = abs(quantity * (fill - raw_mid))
funding_cost = direction * funding_rate * quantity * mark_open   # positive when paid
net_pnl = gross_pnl - fee - slippage - funding_cost              # holds to 1e-9

缺少一筆應有的資金費率觀測值是資料失敗,不是零,也不是可以插補的理由。每一列帳本都把毛損益、手續費、滑價、資金費率與淨損益保留為各自獨立的欄位,而上面那條恆等式在整個語料庫上的一致性誤差在5.55e-17報酬單位以內。

項目,全部36個樣本外格子固定分倉資金基點
毛損益+27,454.6
taker手續費-168,288.4
滑價-67,315.4
資金費率-2,147.0
淨損益-210,296.2

投資組合組別

組別來自前推保留區塊的軸向決策樣本外投資組合
不做軸向選擇全部36個格子各1/36
選市場方六種策略等權平均值最好者該市場上的六種策略各1/6
選策略方六個市場等權平均值最好者該策略在六個市場上各1/6

所有分倉報酬都採用固定資金、一單位的最大絕對部位、不複利。空手、缺失與不合格的分倉留在現金,運作中的分倉絕不重新標準化,因此每個組別都帶著相同的100%最大毛曝險預算,而已實現曝險與週轉率是結果而不是設定。夏普可以從不重疊的每日部位帳中描述性地報告,但它絕不用來挑選設定、選擇組別或決定結論。

統計推論

引擎為三個組別建立配對的每日淨報酬,在每個固定的樣本外窗口內,bootstrap重抽以UTC週一到週日為單位的日曆週叢集,若季度邊界切斷某一週,就留下兩個獨立的片段。被抽到的片段裡,所有市場、策略與組別一起移動,這既保留了橫斷面相依性,又不改變一個窗口的叢集數量。

draws = paired_week_cluster_bootstrap(
    daily_by_arm, windows=14, n_boot=10_000, seed=20260809,
)
point = cumulative(market_picker) - cumulative(strategy_picker)   # +193.9 bp
low, high = np.percentile(draws, [2.5, 97.5])                     # -7868.3, +8194.8
ruling = "no_reliable_winner" if low < 0 < high else "winner"

每個次要的雙尾p值,是較小那一側bootstrap尾端機率的兩倍,並套用有限重抽校正(count + 1) / (B + 1),上限為一;接著兩個「選擇者對上不做選擇」的p值進入5%的Benjamini-Hochberg程序,兩者都回傳0.7513。單一登記的主要對比不做多重性調整,而十四個逐窗口差值是描述性的診斷,不是bootstrap的單位。

變異分解

分解使用每一個完整窗口中36個未加權的格子層級樣本外淨報酬,時間點在每個格子的參數凍結之後,並在市場x策略x窗口的平衡立方體上計算正交的固定效果平方和。

ss = orthogonal_fixed_effect_ss(cube)          # market x strategy x window
shares = {k: v / ss["total"] for k, v in ss.items()}
# market 0.46%, strategy 1.94%, interaction 6.98%, time 1.78%, residual 88.84%
boot = [shares_of(resample_weeks(cube)) for _ in range(n_boot)]
critical = np.percentile(max_abs_centered_error(boot), 95)   # 4.92 pp

這些是描述性的平方和占比,不是母體變異數成分;把它們稱作隨機效果的變異數估計,等於宣稱六個刻意挑選出來的市場具有它們並不具備的可交換性。只有當某個成分的點占比最大、而且它相對於另外兩個橫斷面成分的同時區間都不含零時,才會被稱為主導成分。交互作用符合這個條件:市場減交互作用從-11.45到-1.61個百分點,策略減交互作用從-9.96到-0.12個百分點,而市場減策略橫跨-6.40到+3.44個百分點。

clone、移轉與敏感度測試

clone分組是在每個窗口內,依據已選定設定在軸向區塊上帶正負號的每日部位向量決定的,這些向量橫跨全部六個市場串接而成。完全重複一定合併,近似clone只有在每一組交叉配對相關性都至少0.95時才以完全連結法合併,負相關絕不會把反向策略併在一起,而且一對策略必須有至少60個有限、變異數非零的每日觀測值,相關性才算得上有定義。每個窗口都形成六個單獨群組,因此clone合併後的組別等同於主要組別。

移轉性的測試方式,是把每個窗口的36組凍結設定,在軸向選擇區塊與對應的樣本外區塊上,於全部六個目標市場重播,成本、資金費率、部位規模與執行方式完全相同。移轉階段是逐窗口跑一次而不是彙總跑,因為彙總呼叫會讓較晚的樣本內期間,參與一個以較早樣本外期間評分的選擇。

在結果出來之前就固定下來的登記敏感度清單包括:以擴展的上市以來歷史作為參數區塊、固定為正1 bp的資金費率事件、clone合併後的策略軸、以期望值而非總損益評分的移轉測試、六個留一市場的選擇者面板,以及僅供描述用的2026年7月不完整窗口。主要結果可見之後沒有再新增任何敏感度測試,也沒有任何一項可以取代主結果。

估計市場減策略95%區間
登記的主要估計+193.9 bp-7,868.3 至 8,194.8
擴展的參數歷史-1,596.7 bp-8,488.4 至 5,322.1
固定1 bp資金費率+1,359.1 bp-6,012.0 至 8,808.5
拿掉的市場市場減策略選市場方選策略方
BTC+928.4 bp-4,979.0-5,907.4
ETH+345.3 bp-3,320.1-3,665.4
DOGE+4,064.3 bp-3,646.1-7,710.4
XRP+940.3 bp-4,278.4-5,218.7
BNB-4,494.8 bp-6,474.6-1,979.7
SOL+465.2 bp-4,795.8-5,260.9

預先登記關卡

在最後一次相關的程式碼變更之後、任何樣本外K棒被讀取之前,有十九項阻斷性關卡必須通過,每一項都附帶一個植入的缺陷,用來證明這道關卡真的會失敗。它們涵蓋:資料、策略、搜尋、成本與分析之間的雜湊一致性;六個家族在六個市場上的真實資料訊號、方向與活躍度檢查;一個會實際過濾東西的壓縮過濾器;截斷未來資料之後,先前每一個訊號、候選設定與已完成交易都維持不變;沒有任何資金費率、成本、高低點時序或結果欄位進入訊號;Python與Rust在真實資料切片上的執行一致性;一分鐘路徑順序會改變一個植入的雙邊都觸及結果;資金費率的正負號與筆數與來源資料吻合;淨損益恆等式在單筆交易與加總層級都成立;全部64次嘗試都被保存下來,包含失敗的嘗試;區塊邊界有序、已purge且互斥;一個轉置過的植入面板會精確互換兩個選擇者;組別的分倉數量與權重沒有現金重新標準化;植入的clone對照組產生預期的分群;一個植入的未來結果會實質改善最佳化器並且被偵測到;窗口內的行動輪替精確保留週轉率、同時破壞植入的關係;關卡失敗會往外傳出非零的結束代碼;以及就緒狀態回報登記的檢查為可執行,而不是默默地只跑一部分。

其中最後一項是虛無語料庫關卡:在一個僅供測試的零漂移隨機漫步語料庫上,跨30個獨立種子,沒有任何家族以樣本內選出的樣本外平均值,擁有完全在零以上的95% bootstrap區間。

驗證結果

  • 語料庫:31,770,479筆候選交易列,16,826筆入選的樣本外交易。
  • 成本恆等式:一致性誤差為5.55e-17報酬單位。
  • 引擎一致性:72組真實資料案例、25,259筆交易,最大絕對損益差異1.94e-16。
  • 同根K棒洩漏篩檢:相關性-0.0001,對照未來報酬的+0.0051。
  • 邊界檢查:零筆交易跨越或落在自己登記的樣本外窗口之外。
  • Backtest guard:在walk-forward、帳本、成本與以週為叢集的不確定性檢查上,零項自動化失敗。

兩項自動化警告是靠直接核對資料、而不是靠爭辯解決的。通用檢查把樣本內與樣本外共用的時間戳讀成零天的purge間隔,但引擎是在交易層級做purge,直接清點邊界就解決了這件事。同一套檢查裡以夏普為導向的輸入階段回報了低檢定力與各家族之間的雜訊尺度差異,這不影響一項以損益判定的比較,但確實排除了把這裡任何單一家族當成已發現優勢來呈現的可能。

pre-OOS freeze   b7c102a3a6cbbbb0752e174cc610ed7c56a0f9fe8f94fd42ef184fa16ccd12cb
engine manifest  f2e70f0c2334751029be76673e7f8a429413944ef8026af149c1c9730f717a78
results          4cdf4ac1b42420f480528f3db6323b2e12b7d21ac160d99712e08983dc35451d
data manifest    34a26b3380223fe19afb73cf3d63a60d9e991fba82917e42adc8e7d66adae1e3

在樣本外結果可見之後的任何變更,都需要新的執行根目錄、新的雜湊值與一份書面修訂說明。目前存在兩份修訂,都在凍結之後,也都只涉及呈現:一份移除了窗口匯出中重複的欄位改名,另一份還原了一個被縮短的圖表標籤、把窗口邊界寫成不帶時區的datetime,並讓檢查執行器在任何被要求的檢查失敗時回傳失敗。兩份都沒有碰到資料、設定、交易、報酬、投資組合、統計推論或判定結果。

筆記中的圖表是根據 已發布的結果數據表繪製,那些表格也正是文字中數字的來源。

索取完整流程程式碼

這一頁的內容,已足夠讓你手動核對筆記中的每一個數字,或利用公開的Binance資料重建一項等價的研究。生產環境的腳本、資料建置、Rust引擎、分析與圖表生成並未附在這裡。請寄信至 daniel@daru.finance,我會把它們寄給你。

← 回到筆記