程式碼跑起來沒有錯誤,不代表這個邏輯是對的——尤其是當這段邏輯的證據,其實只來自一次historical事件的時候。
背景
手上有一支跑在 XQ 全球贏家上的期貨自動交易策略:偵測短時間內的來回震盪,判定為「微失衡震盪」後用限價單去賭這段震盪會被打回去——價格靠近低點就做多賭反彈,靠近高點就做空賭回落,屬於典型的均值回歸(mean reversion)策略。
策略上線一段時間後,log顯示一個不好處理的現象:沒有單筆爆虧,但一堆小虧損疊加起來還是淨損失。逐筆核對委託事件log後,確認進場、動態停損停利、逾時撤單這些機制都跟程式碼邏輯吻合,不是bug。問題比較像是策略本身在某些盤勢下勝率structurally偏低——這支策略的停損停利距離設計上最差是1:1,等於勝率至少要接近五成才打得平,但實測期間只有三成多到五成出頭。
於是很自然會想到同一個方向的解法:能不能在下單之前,先判斷一下現在的盤勢對這個方向有沒有利,不利就先不出手? 這篇記錄的是兩次嘗試這個想法的過程,以及為什麼兩次最後都決定不把它寫進正式跑的交易程式碼。
重點
嘗試一:用價格趨勢過濾進場
第一個想法:如果過去一段時間(例如10〜20分鐘)價格已經明顯朝某個方向走了一段,那眼前這個「低點」很可能只是那段趨勢裡的一步,不是真的跌到底要反彈——這時候去做多賭反彈,比較像是接刀子,不是抓到真正的區間震盪。
定義一個簡單的指標:
drift(N分鐘) = 進場價 − N分鐘前的價格
align(N分鐘) = drift × 進場方向(多=1/空=-1)
align 為負,代表這筆單是在跟過去N分鐘的價格走勢對做(逆勢fade);align 為正或零,代表沒有明顯跟這筆單對做的長期走勢。
因為沒有額外的逐tick資料庫,改用委託事件log裡「每一筆事件都帶TickPrice+Date+Time」這個特性,把全部事件依時間排序重建出一條(不連續但涵蓋全程的)價格路徑,回頭對每一筆已出場交易查詢「進場前N分鐘」最接近的價格。第一輪看起來很有希望:10分鐘回看窗口下,align為負的交易勝率44.4%,align為正的交易勝率75.0%,差距不小。
但只看這一組數字就下結論太快。改用Fisher精確檢定,對5個不同回看窗口(5/10/20/30/60分鐘)分別做「align正負」跟「輸贏」的獨立性檢定:
| 回看窗口 | 逆勢勝率 | 不逆勢勝率 | Fisher exact p值 |
|---|---|---|---|
| 5分鐘 | 50.0%(n=46) | 60.0%(n=15) | 0.5625 |
| 10分鐘 | 44.4%(n=45) | 75.0%(n=16) | 0.0448 |
| 20分鐘 | 45.2%(n=42) | 68.4%(n=19) | 0.1068 |
| 30分鐘 | 44.1%(n=34) | 63.0%(n=27) | 0.1982 |
| 60分鐘 | 52.9%(n=34) | 51.9%(n=27) | 1.0000 |
只有10分鐘這個窗口勉強壓在傳統0.05門檻之下,其他全部不顯著,60分鐘甚至完全看不出關聯。更關鍵的問題是:這是同時測了5個窗口才挑出「10分鐘最好看」,這本身是多重比較問題——如果套用最簡單的Bonferroni校正(門檻除以測試次數),連這個10分鐘的結果都不再顯著。而且如果align真的是個穩健的效應,通常鄰近的窗口(5分鐘、20分鐘)也該出現類似但稍弱的訊號;但實際看到的p值序列是 0.56 → 0.045 → 0.11 → 0.20 → 1.0,完全不連續。這種「只有一個特定參數值冒出來」的形狀,統計上更像是雜訊剛好卡到門檻,不是真實效應。
結論:這個方向直覺合理,但目前的樣本證明不了,先不當成擋單規則,改成只用外部Python腳本事後分析log,累積更多資料後再重新檢定。
嘗試二:連續停損就先暫停(斷路器)
同一份log裡還發現另一個現象:有一段長達2小時10分鐘的區間,連續出現10次停損,而且多空方向交錯(多單停損、空單停損、又是多單……)。策略原本已經有「同方向冷卻」機制(某方向停損後暫停該方向一段時間),但這個機制是每個方向獨立計時,抓不到「多空輪流虧」這種盤況——兩邊各自的冷卻時間都還沒到,於是各自恢復進場,整段時間其實一直在虧。
順理成章的解法:加一個不分方向的「全局斷路器」——不分方向累計連續停損次數,達到門檻(例如5次)就暫停全部新進場一段時間(例如15分鐘),中間只要出現一次真正的停利就歸零。邏輯合理、實作也不複雜,很快就寫進了策略程式碼。
寫完之後,回頭套用跟align完全一樣的檢驗標準:連續停損次數,對「下一筆交易」到底有沒有預測力? 用歷史交易序列算出「這筆交易發生前,已經連續停損幾次」,分組看勝率:
| 前面連續停損次數 | 樣本數 | 這筆交易勝率 |
|---|---|---|
| 0 | 33 | 57.6% |
| 1 | 13 | 61.5% |
| 2 | 5 | 40.0% |
| 3 | 3 | 66.7% |
| 4+ | 7 | 14.3% |
最後一格看起來很支持斷路器的假設——但仔細看樣本會發現一個更根本的問題:歷史上總共只發生過一次「連續停損達4次以上」的事件,就是前面提到的那段2小時10分鐘、連虧10次的區間。那格n=7,不是7個獨立觀察到的案例,而是同一段連續事件裡的7筆交易。這其實接近循環論證:「一段很長的連續虧損裡,交易大多是輸的」,這句話在定義上幾乎必然為真,並不代表「連續停損次數」這個訊號本身有預測力、可以推廣到未來另一段還沒發生的連續虧損。
換句話說,斷路器背後「連續停損代表盤勢不利」這個假設,證據基礎甚至比align更薄弱——align至少還有數十筆分散樣本,斷路器的核心證據其實只是同一個歷史事件被重複計算了幾次。最後決定把這段程式碼整個移除,回到只有「同方向冷卻」的版本。
共通的原則:分清楚「預測性宣稱」跟「風控性宣稱」
回頭看這兩次嘗試,會發現一個可以複用的判斷架構:任何想加進交易系統的規則,最終都是在做以下兩種宣稱的其中一種,而這兩種宣稱需要的證據門檻完全不同。
預測性宣稱:「這個可觀測的量,能預測交易結果」(例如align、連續停損次數)。這種宣稱需要真正的統計證據:獨立樣本數是否足夠、有沒有做多重比較校正、鄰近參數值的結果是否呈現平滑一致的模式(而不是單一參數值異常突出)。align跟斷路器都是這種宣稱,也都沒通過檢驗。
風控性宣稱:「不管有沒有預測力,都要限制最壞情況能壞到什麼程度」(例如單筆停損點數、最大部位)。這種宣稱不需要證明「有效」才能存在,只需要接受「限制下檔」本身的成本效益值得——就像沒有人會要求先證明「停損點數設30點會提升勝率」才能用停損。
問題出在:斷路器當初被提出時,用的說法其實是「這段行情不管賭哪邊都不管用,應該全部先停手」——這是預測性宣稱的包裝,不是風控性宣稱。如果一開始就誠實地把它定位成「策略層級的停損」(不宣稱有效、純粹限制最大連續虧損),那證據門檻會不一樣,可能就不需要拿掉。但既然設計時的論述走的是預測路線,就該用預測路線的證據標準檢驗它,檢驗不過就该拿掉,不能中途換一套比較寬鬆的標準幫它續命。
注意事項
- 檢查樣本是不是真的獨立:分組統計時,n=7 不代表7個獨立事件,有可能是同一段連續事件被切成7筆重複計算。遇到「連續」「累積」類的指標尤其要注意這一點。
- 多重比較要校正或至少要誠實揭露:掃描多個參數值(回看窗口、門檻)之後只報告最好看的那組,等於是在雜訊裡挑訊號,一定要用Bonferroni之類的方法校正,或者至少誠實講清楚測試過幾組。
- 鄰近參數值的結果不連續,是警訊不是巧合:真實效應通常在鄰近參數上會有類似強度,只有單一參數值特別突出、其餘都不顯著,比較像雜訊剛好卡到門檻。
- 先想清楚這是預測性宣稱還是風控性宣稱:兩種宣稱要求的證據完全不同,混著講很容易讓一個沒驗證過的預測性假設,披著風控的外衣被放行。
- 正式上線的程式碼,跟探索性分析要實體分開:只把通過驗證的機制放進交易程式碼,任何還在假設階段的「盤勢判斷」留在外部分析腳本裡,用匯出的log重新檢驗,通過門檻才「升級」進正式程式碼。
結語
這支策略最後回到一個比較樸素、但更誠實的分工:交易程式碼裡只留下已經逐筆核對過log、確認邏輯正確的機制(震盪偵測、動態停損停利、同方向冷卻、不明部位強制出清);任何還沒被證明的「猜盤勢」想法,都留在外部分析腳本裡繼續觀察,資料夠了再重新檢定一次,而不是先寫進去、之後再回頭驗證。統計檢定沒有讓策略變得更聰明,但確實讓它少犯了兩次「感覺很合理」的錯。