← 回到文章
可重現性

文章裡的每個數字,以及它從哪裡來

方法寫到足以讓你自己把帳算一遍,或用相同的公開與授權資料重建一份等價的研究。

資料與標的池

兩個面板,都是 15 分鐘與一小時 K 線,期間從 2021 年 1 月到 2026 年 7 月。加密貨幣是幣安 USDT 保證金永續合約的 OHLCV,附上逐合約逐月實測的報價價差,以及每一筆資金費率事件在其結算時間戳上的紀錄。股票是經分割調整的合併 K 線,只納入正規交易時段內符合 NBBO 資格的成交,排除零股與場外成交回報,以永久證券識別碼而不是股票代號作為鍵值,並帶有每根 K 線實測的時間加權 NBBO 價差。

標的池在 2022 年 12 月 31 日固定,只依 2022 年 10 月到 12 月的流動性決定,每個市場取流動性最高的十檔。MATIC 在 2024 年 9 月停止交易,之後就讓它缺席,而不是找替代品,因為事後挑選的替代品會用到「哪些合約活了下來」的資訊。

市場標的
加密貨幣BTCUSDT, ETHUSDT, DOGEUSDT, XRPUSDT, BNBUSDT, SOLUSDT, MATICUSDT, LTCUSDT, CHZUSDT, ETCUSDT
股票TSLA, AAPL, NVDA, AMZN, MSFT, AMD, META, GOOGL, NFLX, XOM

規則庫

十四個家族,每個三十組參數,共 420 條規則。對每個家族,生成器先列舉下表的網格,丟掉無效與重複的設定,再以種子 20260804 取出 30 組空間填充的設定。規則庫以標準形式序列化並計算雜湊,只要雜湊不同,每一個 runner 都會拒絕啟動。

家族第 t 根 K 線收盤時的訊號網格
趨勢追蹤站在移動趨勢的那一側,前提是趨勢強度越過門檻回看 12, 24, 48, 96;強度 0, 0.5, 1 個波動單位;持有 1, 2, 4, 8, 16
均值回歸反向操作相對於滾動均值的標準化偏離回看 12, 24, 48, 96;進場 z 0.5, 1, 1.5, 2;持有 1, 2, 4, 8
突破收盤價突破先前滾動收盤價的極值回看 6 到 96;緩衝 0, 5, 10 bp;持有 1, 2, 4, 8
動能落後累積報酬的正負號,強度須高於門檻回看 3 到 96;強度 0, 0.25, 0.5, 1 個波動單位;持有 1, 2, 4, 8
波動擴張短期波動超過長期波動時,跟隨短期方向短期 6, 12;長期 24, 48, 96;比值 1.25, 1.5, 2;方向 1, 3;持有 1, 2, 4
波動收縮壓縮期間跟隨短期方向短期 6, 12;長期 24, 48, 96;比值 0.4, 0.6, 0.8;方向 1, 3;持有 1, 2, 4
均線交叉已完成 K 線上的快/慢 EMA 交叉快線 3 到 24;慢線 12 到 96,快線短於慢線;持有 1 到 16
通道突破收盤價突破均值加減 ATR 通道回看 12 到 96;寬度 0.5, 1, 1.5, 2 ATR;持有 1, 2, 4, 8
RSI 反轉超賣買進,超買賣出RSI 6, 14, 28;界線 20/80, 30/70, 40/60;持有 1, 2, 4, 8
Bollinger 反轉反向操作收在通道外的收盤價回看 12, 20, 48, 96;寬度 1 到 2.5 個標準差;持有 1, 2, 4, 8
Donchian 突破收盤價突破先前的高低點通道回看 12 到 96;持有 1 到 16;可選一根 K 線的確認
成交量確認只有在標準化成交量越過比值時才做動能動能 3 到 24;成交量回看 12 到 96;比值 1, 1.5, 2;持有 1, 2, 4, 8
開盤區間突破突破交易日最初幾根 K 線的區間區間 1, 2, 4 根 K 線;緩衝 0, 5, 10 bp;最晚進場為當日的 0.5, 0.75;持有 1, 2, 4
時間型固定的時段區塊與方向時段比例 0, 0.25, 0.5, 0.75;做多或做空;星期遮罩;持有 1, 2, 4, 8

訊號是一次進場轉換:規則第一次進入做多或做空狀態時觸發,停留在同一狀態的後續 K 線不算新訊號。在每個 24 個月的訓練視窗內,一條規則只有在完成的交易少於 120 筆、交易的標的少於 5 個,或產生的訊號集合與一條編號較小的規則完全相同時,才會被剔除,從來不會用任何獲利統計量來剔除規則。

有七組波動擴張設定從未交易,因為在那些視窗長度下,它們要求的短期對長期波動比值根本達不到;而重複檢查在比較訊號時不看持有期,所以只差在持有多久的規則,會併成編號最小的那一條。平均而言,420 條規則中每個視窗有 392 到 396 條符合資格。

執行

決策在第 t 根 K 線完成之後做出。部位在第 t+1 根 K 線的開盤價進場,在持有的第 h 根 K 線收盤時出場,h 是規則以觀察到的 K 線根數計的持有期。沒有停損也沒有目標價,因此一根 K 線高點與低點的先後順序永遠不會決定出場。

每一組「規則-標的」最多持有一個候選部位,在部位未平倉期間到來的訊號會被忽略;模型跳過一筆交易,也不會因此產生替補交易。

entry = signal_idx + 1
exit  = signal_idx + hold                       # close of the hold-th held bar
gross_bps = direction * log(close[exit] / open[entry]) * 1e4
net_bps   = gross_bps - spread_bps - fee_bps - slippage_bps + funding_bps

特徵

六十二個價格與成交量特徵,在第 t 根 K 線上、只用各標的自己的歷史計算:1 到 24 根 K 線的落後報酬與動能、高低區間、K 線實體與影線的比例、收盤到開盤的跳空、波動、與均線的距離、在區間中的位置,以及 6 到 96 根 K 線的高低點距離,加上標準化成交量。另外再加七個:趨勢強度(24 根 K 線均線距離的絕對值除以 24 根 K 線的波動)、以正弦與餘弦成對表示的時段位置與星期、交易日剩餘的 K 線根數,以及第 t 根 K 線收盤價的對數。

規則本身提供:以 one-hot 向量表示的家族、依網格位置縮放的參數與缺漏參數旗標、訊號的方向與強度、預定持有期,以及距離上一個訊號的 K 線根數。近期表現是這條規則在該標的上最近 20 筆已完成交易的平均、總和、勝場與敗場,以及它跨標的最近 100 筆已完成交易的平均、勝場與敗場,只計入在決策 K 線當下或之前已經出場的交易。

# strategy-level trailing 100: only trades that have already exited count
for p in trades_sorted_by_entry:
    while exits[cursor].exit_ts <= p.signal_ts:
        ring.push(exits[cursor].net_bps); cursor += 1
    p.recent_100_mean = ring.mean()

執行資料(下一根 K 線的開盤價、每一個成本欄位與資金費率)存放在每一列上,並從模型矩陣中移除。時段位置與剩餘 K 線根數使用當日的 K 線數量,因此在有缺漏 K 線的日子裡會反映出缺口;依格子不同,這些日子承載了 0.002% 到 0.33% 的交易,把它們剔除後,最好的帳戶從 +0.748% 變成 +0.761%。

在訓練視窗內進場、卻在視窗之後才出場的交易,會同時從訓練集與樣本外集合中排除,所以它也不會進入那 20 筆交易的歷史,這讓該歷史稍微過時一點,但永遠不會超前於決策。

模型與搜尋

三個家族,每個都分別針對淨報酬目標與獲利目標擬合,各有 30 組固定設定。迴歸目標在訓練資料的 0.5% 與 99.5% 分位數處截斷並標準化。失敗、輸出為常數與未收斂的設定都計入那 30 組,且不能被選中。

家族實作30 組設定的網格範圍
線性純 L2 用 Ridge,其餘用 elastic net;獲利目標用 logistic elastic netalpha 0.0001 到 0.03;L1 比例 0 到 1
提升樹LightGBM,150 棵樹,不做列或欄抽樣深度 2 到 6;葉節點 4 到 31;學習率 0.0054 到 0.091;最小子節點 104 到 4,645;L2 0.0014 到 68
神經網路GELU MLP,6 個 epoch,批次 8,192深度 1 到 3;寬度 16 到 128;學習率 0.0001 到 0.0028;權重衰減 1.5e-7 到 0.0094

在每個視窗內,訓練列依時間順序以 80/20 切成內層訓練與驗證,切分點之前移除 16 根 K 線的時鐘時間。淨報酬模型依驗證集上的 Spearman rank IC 選擇,獲利模型依驗證集上的 ROC AUC 選擇,平手時取設定編號較小者。勝出的設定用完整 24 個月重新擬合,其前處理也只在這些資料列上擬合。

Walk-forward

四十三個每月樣本外視窗,從 2023 年 1 月到 2026 年 7 月,四個「市場-時間框架」格子各一套,共 172 個視窗。每個視窗恰好以前 24 個日曆月訓練,只使用進場與出場都落在其中的交易,然後對下一個日曆月進場的每一筆合格交易預測一次。每個市場、時間框架、家族與目標各一個模型,合併所有規則與標的。

for cell in ["equity_1h", "equity_15m", "crypto_1h", "crypto_15m"]:
    for month in months("2023-01", "2026-07"):
        is_rows  = trades(entry >= month - 24M, exit < month, eligible_rules(month))
        oos_rows = trades(month <= entry < month + 1M, eligible_rules(month))
        for family in ("linear", "gbt", "mlp"):
            for target in ("net_return", "profit"):
                best = select(family, target, is_rows, n_configs=30)   # 80/20 inner split
                model = refit(best, is_rows)
                oos_rows[f"pred_{family}_{target}"] = model.predict(oos_rows)

總計從 30,960 組嘗試過的設定中選出 1,032 個模型,其中 30,394 組擬合成功,並在 39,908,684 筆候選交易上產生 239,452,104 個樣本外預測。

交易策略

每一組「規則-標的」都拿到相同的基礎權重 w0 = 1 / (S × 10),其中 S 是該視窗內合格規則的數量,因此每個訊號都做永遠不會超過 100% 的總曝險。

baseline          w = w0
simple filter     w = w0 if at least 2 of { vol_24, trend_strength_24, vol_norm_24 }
                               exceed the instrument's median over the 24 training months
ML filter         w = w0 if prediction > 0 bp (net return) or > 0.5 (profit)
ML sizing         q = rank of prediction in the window's sorted validation predictions
                  w = w0 * (0.25 + 1.5 * q)
ML ranking        at each signal timestamp keep ceil(20%) of the new signals, at least one,
                  by prediction; w = w0 * min(5, n_candidates / n_kept)

簡單過濾條件的中位數取自訓練視窗中不重複的市場 K 線,而不是交易列,所以一根帶有許多訊號的 K 線不會被重複計算。一個過濾在某個月一筆交易都沒做,那個月就持有現金。

成本

市場價差每次成交手續費每次成交滑價資金費率
加密貨幣進場付實測價差的一半,出場再付一半5 bp2 bp從進場 K 線到出場 K 線之間的每一筆實際事件,帶正負號
股票進場付每根 K 線實測價差的一半,出場再付一半0.5 bp0

資金費率為正時多單被扣款、空單收款,而資金費率事件歸屬於包含它的那根 K 線,所以在結算 K 線開盤進場的交易要付那次結算。與手續費相比,資金費率一直很小:每個加密貨幣一小時訊號都做,整段期間資金費率淨額是資本的 -0.28%,手續費則付了 174.59%。每一種策略的成本都相同,這就是為什麼文章裡每筆交易的成本柱幾乎不動,而毛利柱會動。

統計推論與結論門檻

經濟數字來自串接起來的樣本外帳本,以及一條不重疊的每日報酬序列,損益在每筆交易出場時入帳。文章報告的是每日報酬的加總,因為複利會讓每一個加密貨幣帳戶都卡在 -100% 附近;登記的淨報酬門檻則使用複利序列。

每個對照都是一種策略在每日序列上減去其比較對象。不確定性用配對 bootstrap 估計,以整週為單位重新抽樣,10,000 次複製,種子 20260804;Benjamini-Hochberg 校正施加在全部 144 個對照上:4 個格子、18 種模型策略與 2 個比較對象,不通過的也包括在內。

weeks  = daily_delta.groupby(week)                        # week-clustered resampling
boot   = [weeks.sample(n_weeks, replace=True).sum() for _ in range(10_000)]
p      = two_sided(boot)
q      = benjamini_hochberg(p_all_144)

# rotation null: same timestamps, same instrument, same count, same exposure budget
for r in range(1_000):
    shifted = circular_shift(scores, offset=nonzero, within=(cell, month, instrument))
    null[r] = net_of_policy(select(shifted))
passes_rotation = policy_net > quantile(null, 0.95)

一個對照在七道門檻全部成立時才獲得認定:淨報酬高於比較對象、每單位平均總曝險的淨報酬高於比較對象、q 低於 0.05、43 個月中贏下超過一半、月報酬風險比大於 1、刪掉最好的那個月之後仍保有正的領先,以及結果高於輪轉虛無對照的第 95 百分位。當一個策略每個月都贏時,報酬風險比沒有定義,這道門檻就不通過;有十二個對照處於這種情況,六個在股票 15 分鐘 K 線,六個在加密貨幣一小時 K 線。

一般性結論需要在兩個市場、兩種時間框架,以及三個模型家族中至少兩個,都有獲認定的結果。文章中針對單一帳戶引用的絕對週數字,來自另一次以週為叢集、對各帳戶自身週損益做的 bootstrap。

檢核

在任何樣本外數字出現之前,面板層級的測試組就確認了:截斷未來之後,先前的每個特徵、訊號與交易都不變(最大差異 0.0);刻意植入的目標洩漏會把驗證 IC 從 0.017 拉到 0.9999;沒有任何成本或資金費率欄位進入模型矩陣;資金費率按事件時間計算,費率為正時多單被扣款;已知的股票分割都有調整;下一根 K 線的目標對齊誤差在 1.7e-6 bp 以內。一個 Python 參考實作在 50,000 根真實 BTCUSDT K 線上,以每個家族各一組設定,與編譯後的訊號引擎在 57,648 筆交易上完全一致。

執行之後,39,908,684 列樣本外候選資料每一列都和來源面板核對過:進場在訊號後一根 K 線、恰好在持有期後出場、進場價等於面板開盤價且出場價等於面板收盤價、毛報酬、價差、手續費、滑價與資金費率都從原始輸入重新計算、淨額恆等式成立、進場落在評分月份內,以及價格特徵等於訊號 K 線收盤價的對數。每一個過濾、部位大小、排序與簡單過濾條件的權重,都從儲存的預測值與訓練期中位數重建,而全部 92 個策略帳戶的合併總額,重建後的誤差在 5.3e-15 以內。

股票價差是隨研究一起凍結的逐 K 線數值。用之後重新抓取的同一批價差為每一筆股票交易重新計價,沒有任何策略的變動超過資本的 0.0024,也沒有任何正負號改變。bootstrap p 值、BH q 值與輪轉虛無對照的超越次數,也都能從每日序列完全重現。

雜湊值

產出物SHA-256
規則庫501f04df5537f881709027e8755eade85d7ca667acb0051d1fcd4b9dc18bb14a
模型網格6c6d89b6cfd435ce875876820443405f5d383956a5673ea38f5d2d410b490e47
交易策略df469ad083dde73c7be52ed5d33ad4d434faa684a74e7777c200b6d5b988bf1d
特徵eedebd594bfa8aa9c56fb282e7d065eef06c99b63a0d07f974668a526862cc02
合併設定6fc808d41d71d9930b17eb81a0b6b0d2189ba9b7e084896e70c61a3020c94f0f
分析設定1e3af923775cf8fca30f2c28da0ecab7803320454cfafd15e9101a33d53b5735
分析實作86f433e42fe14560425f6da690754e5b603509b4b9581bba56d6bd1bd447fb3b

文章圖表畫出的每一個數字,都在 一個 JSON 檔裡。完整的流程,從訊號產生、walk-forward 擬合到稽核腳本,可來信索取: daniel@daru.finance

← 回到文章