2026/04/10

20260410 終於進到 WFA 了

 # Walk-Forward Analysis Flow


## Target Platform

- **CPU**: i9-13900K — 8 P-Cores @ 5.5GHz (E-Cores off, HT off)

- **RAM**: 32GB DDR4-3200

- **SSD**: NVMe Gen4 (ADATA LEGEND 850)

- **Workers**: `--workers 7` (保留 1 core 給 OS + I/O)


## Data: MXF_1m_20200223.csv

- Range: 2020-03-02 ~ 2026-03-31 (1,814 trading days, ~6.1 years)

- Size: 1,683,477 rows (1-min bars)

- All commands run from `..\2Axis\`


## Memory Budget (7 workers)


| Component | Per Worker | 7 Workers |

|-----------|-----------|-----------|

| 1-min OHLCV cache (5 arrays × 1.68M × 8B) | 67 MB | 470 MB |

| K-min data cache (worst: K=1, same as 1-min) | 67 MB | 470 MB |

| Numba trade buffer (200K × 8 cols × 8B) | 12.8 MB | 90 MB |

| date/flag arrays + overhead | ~20 MB | ~140 MB |

| **Total** | **~167 MB** | **~1.2 GB** |


32GB RAM 綽綽有餘。Python 主進程 + Pandas 讀 seed CSV 約佔 200-500MB。


---


## Pre-run: Numba JIT Warm-up


V2 函數簽名已改(新增 `trade_date_start/end`),首次執行需重新 JIT 編譯。

```bash

# 若遇 Numba 編譯錯誤才需手動清(V2 模組名不同,通常不衝突):

rm -rf __pycache__/bt_bb_unpruned_v2.* __pycache__/bt_bb_2nd_unpruned_v2.*

```

首次 JIT 編譯約 3-5 分鐘(Stage 1 + Stage 2 各一次),之後 `cache=True` 生效,後續啟動秒開。


---


## Fold 1: IS 4yr (2020/03-2024/03) + OOS 1yr (2024/04-2025/03)


### Step 1: Stage 1 Signal Optimization (IS)

```bash

python bt_bb_unpruned_v2.py --mode opt ^

  --start-date 2020-03-02 --end-date 2024-03-31 ^

  --output data/fold1_s1_unpruned.csv ^

  --workers 7

```

- 1,166,400 combos (5 K-bar x 3 axis x 2 neutral x 21 BB x 16 ROC x 8 Flat x POC combos)

- 含 Plateau 26-grid 鄰域檢定

- Output: `data/fold1_s1_unpruned.csv`

- **預估: 30-45 min** (首次 +5 min JIT)


### Step 2: Filter Seeds

```bash

python filter_seeds.py data/fold1_s1_unpruned.csv ^

  --output data/fold1_seeds.csv ^

  --years 4.0

```

- Plateau=Pass, PF>=1.5, Trades>=150, RF>=3, Calmar>2

- `--years 4.0` 對應 IS 期間,確保 Calmar/Recovery 計算正確

- **預估: < 5 秒**


### Step 3: Stage 2 Risk Optimization (IS)

```bash

python bt_bb_2nd_unpruned_v2.py data/fold1_seeds.csv ^

  --output data/fold1_s2_IS.csv ^

  --start-date 2020-03-02 --end-date 2024-03-31 ^

  --workers 7

```

- Risk grid: ~432 combos/seed (SL×TP×Trail×TimStop, pruning 後)

- Numba 在 1-min 解析度逐棒掃描 1.68M bars × 432 combos/seed

- Output: `data/fold1_s2_IS.csv` (31 cols, 含 S1 績效)

- **預估: 60-90 min** (取決於種子數量,通常 ~2,000-3,000)


### Step 4: Stage 2 OOS Validation

```bash

python bt_bb_2nd_unpruned_v2.py data/fold1_seeds.csv ^

  --output data/fold1_s2_OOS.csv ^

  --start-date 2024-04-01 --end-date 2025-03-31 ^

  --workers 7

```

- 相同種子、相同風控網格,不同日期窗口

- 用於 IS vs OOS 比較,偵測過度擬合

- **預估: 60-90 min** (與 IS 相同,因為全量資料都需載入)


**Fold 1 小計: ~2.5-4 小時**


---


## Fold 2: IS 5yr (2020/03-2025/03) + OOS 1yr (2025/04-2026/03)


### Step 1: Stage 1 Signal Optimization (IS)

```bash

python bt_bb_unpruned_v2.py --mode opt ^

  --start-date 2020-03-02 --end-date 2025-03-31 ^

  --output data/fold2_s1_unpruned.csv ^

  --workers 7

```

- **預估: 30-45 min** (Numba cache 已存在,無 JIT 延遲)


### Step 2: Filter Seeds

```bash

python filter_seeds.py data/fold2_s1_unpruned.csv ^

  --output data/fold2_seeds.csv ^

  --years 5.0

```

- **預估: < 5 秒**


### Step 3: Stage 2 Risk Optimization (IS)

```bash

python bt_bb_2nd_unpruned_v2.py data/fold2_seeds.csv ^

  --output data/fold2_s2_IS.csv ^

  --start-date 2020-03-02 --end-date 2025-03-31 ^

  --workers 7

```

- **預估: 60-90 min**


### Step 4: Stage 2 OOS Validation

```bash

python bt_bb_2nd_unpruned_v2.py data/fold2_seeds.csv ^

  --output data/fold2_s2_OOS.csv ^

  --start-date 2025-04-01 --end-date 2026-03-31 ^

  --workers 7

```

- **預估: 60-90 min**


**Fold 2 小計: ~2.5-3.5 小時** (無 JIT 開銷)


---


## Full-Period Baseline (Optional)


不限日期,完整 6.1 年:

```bash

python bt_bb_unpruned_v2.py --mode opt ^

  --output data/full_s1_unpruned.csv --workers 7


python filter_seeds.py data/full_s1_unpruned.csv ^

  --output data/full_seeds.csv --years 6.1


python bt_bb_2nd_unpruned_v2.py data/full_seeds.csv ^

  --output data/full_s2.csv --workers 7

```

- **預估: ~2.5-3.5 小時** (Stage 1 ~35min + Stage 2 ~90min)


---


## Runtime Summary


| Step | Per Fold | 瓶頸 |

|------|----------|------|

| Stage 1 (1.16M combos) | 30-45 min | Numba Tier-3 OMS loop × K-bar bars |

| Filter seeds | < 5 sec | Pandas I/O |

| Stage 2 IS (~2.5K seeds × 432 RC) | 60-90 min | 1-min 解析度 1.68M bars × 432 combos/seed |

| Stage 2 OOS (same) | 60-90 min | 同上 |

| **Per Fold Total** | **2.5-4 hr** | |


| Scenario | Estimated Total |

|----------|----------------|

| **2-Fold WFA (recommended)** | **5-8 小時** |

| + Full-Period Baseline | +2.5-3.5 小時 |

| **Complete (2 folds + baseline)** | **7.5-11.5 小時** |


### Performance Notes

- **Stage 1 瓶頸是 K=1**:1.68M 根 1 分棒佔全部 K-bar 時間的 ~40%;K=10 只需 168K 根,不到 2 分鐘

- **Stage 2 瓶頸是 seed 數量**:每顆種子需 432 次 1-min 全量掃描(~14 sec/seed @ 5.5GHz)

- **7 workers 是甜蜜點**:8 workers 會搶 OS 資源,6 workers 浪費一顆 P-Core

- **Numba cache 一次性**:首輪 JIT 約 3-5 min,後續 `cache=True` 直接載入

- **記憶體峰值 ~2 GB**:32GB DDR4-3200 不會成為瓶頸


### Optimization Tips

- 如果跑過夜,建議 Process Lasso 暫時移除 Core 綁定(釋放全部 8 cores 給 Python)

- 可用 `--workers 8` 壓榨最後一核,風險是 OS 偶爾卡頓(backtest 不影響正確性)

- Stage 2 的 seed 排序已按 K_Bar 排列(worker cache 命中率最高)


---


## WFA Validation Criteria


策略通過 WFA 的條件:

1. **OOS PF >= 1.2** (IS 門檻 1.5 的 80% 折扣)

2. **OOS Win Rate >= 45%**

3. **IS→OOS PF 衰退 < 40%** (PF_OOS / PF_IS > 0.6)

4. **OOS Net Profit > 0**

5. **兩個 Fold 都出現在 top N** (跨 Fold 穩定性)


## Analysis After All Runs Complete


提供以下 CSV 即可分析:

- `fold1_s2_IS.csv`, `fold1_s2_OOS.csv`

- `fold2_s2_IS.csv`, `fold2_s2_OOS.csv`

- (Optional) `full_s2.csv`


我可以產出:

1. Top 5 WFA-validated strategies (cross-fold intersection)

2. IS vs OOS 績效比較 Excel(含 PF decay、Calmar decay)

3. Overfitting Risk Score per strategy

4. Final agent config recommendation


## Date Format


兩個階段都接受 `YYYY-MM-DD` 或 `YYYYMMDD`(dash 自動移除)。

`filter_seeds.py` 無日期參數,用 `--years` 校正 Calmar/Recovery 計算。


---


## V2 Changes Summary


| Fix | Stage 1 | Stage 2 |

|-----|---------|---------|

| Entry price: next-bar Open (non-repainting) | Fixed | Already correct |

| Signal from `oms_position_arr[i-1]` | Fixed | Already correct |

| WFA date range (`--start-date`, `--end-date`) | Added | Added |

| Data file override (`--data-file`) | Added | Added |

| Worker initializer (Windows spawn safe) | Added | Added |

| S1 columns carried through | N/A | Yes (from Phase 3) |

| 6-year data default | Yes | Yes |

---

> 可是我們這種回測法沒有去考慮台股有除權息讓隔月指數跳空的問題, 回測資料可信?

● 好問題。分兩層來看:

  1. MXF 期貨 vs 現貨指數 — 影響比你想的小

  除權息讓加權指數機械式下跌(每年 6-8 月累計約 200-400 點),但你的資料是 MXF 期貨成交價,不是現貨指數。期貨的特性:

  - 市場在除息前就透過基差收斂把預期股息 price-in 了
  - 除息當天期貨跳空幅度遠小於現貨(基差已提前反映)
  - 你看到的 MXF_1m_20200223.csv 是交易所撮合價,不是用現貨推算的

  2. 你的策略是日內 — 進一步降低影響

  看你的系統設計:

  - is_closing 強制收盤前平倉 → 不持有跨盤部位
  - 夜盤 5:00 前平 → 隔天 8:45 開盤是空手 → 不吃除息跳空缺口
  - BB/ROC 指標在開盤後幾根 K 棒就會吸收跳空,影響只在前幾根 bar

  所以除權息對你的回測影響是:

  ┌────────────────────┬──────────┬────────────────────────────────────┐
  │        層面        │ 影響程度 │                原因                │
  ├────────────────────┼──────────┼────────────────────────────────────┤
  │ 部位直接虧損       │ 極低     │ 不持倉過夜                         │
  ├────────────────────┼──────────┼────────────────────────────────────┤
  │ 指標失真(BB/ROC) │ 輕微     │ 開盤幾根 bar 噪音,之後自動修正    │
  ├────────────────────┼──────────┼────────────────────────────────────┤
  │ 6 年回測平均化     │ 被稀釋   │ 6 個除息季,WFA 兩 fold 各涵蓋多次 │
  └────────────────────┴──────────┴────────────────────────────────────┘

  3. 真正要擔心的不是除權息,而是:

  期貨轉倉(換月) — 你的 CSV 是連續資料,近月→次月換倉時也有跳空。但同樣因為日內交易不持倉過夜,影響很小。

  結論:以你這套日內策略 + session close 強制平倉的架構,除權息不構成回測可信度問題。回測結果可以信任,不需要特別處理調整後資料。

  如果你之後想做隔夜持倉策略,那就必須處理這個問題了。

✻ Cogitated for 58s

> 日內交易? 它的值我都設為0, 而且都也沒進去當變數回測, 有這個 config 只是讓自己未來的實盤有彈性而已

● 你說得對,我前一個回答判斷錯了。EXECUTION_CLOSE_MINUTES = 0 表示不強制收盤前平倉 → 部位可以跨盤持有過夜。

  讓我重新確認一下系統實際行為。

  Searched for 1 pattern, read 1 file (ctrl+o to expand)

● 確認了:close_minutes=0 → is_closing_1m 全部 False → step3 強制平倉永遠不觸發 → 部位可以跨日盤/夜盤/隔夜持有。

  修正我的判斷

  上一個回答的前提(「不持倉過夜」)是錯的。實際情況:

  部位可能橫跨以下時段:
  - 日盤 13:45 → 夜盤 15:00(1h15m gap)
  - 夜盤 05:00 → 隔天日盤 08:45(3h45m gap)
  - 跨週末(週五夜盤 → 週一日盤)
  - 跨除權息日

  但結論沒變:回測仍然可信

  原因是你的資料是 MXF 期貨成交價,不是現貨加權指數:

  ┌────────────────┬──────────────────────────┬─────────────────────────────────────┐
  │                │       現貨加權指數       │        MXF 期貨(你的資料)         │
  ├────────────────┼──────────────────────────┼─────────────────────────────────────┤
  │ 除息日跳空     │ 機械式全額反映           │ 已透過基差提前 price-in,跳空幅度小 │
  ├────────────────┼──────────────────────────┼─────────────────────────────────────┤
  │ 持多單過除息   │ 虧損 = 全額股息點數      │ 虧損 = 殘餘跳空(遠小於現貨)       │
  ├────────────────┼──────────────────────────┼─────────────────────────────────────┤
  │ 需不需要調整? │ 需要(否則高估空單獲利) │ 不需要 — 價格就是真實成交價         │
  └────────────────┴──────────────────────────┴─────────────────────────────────────┘

  關鍵點:你的 PnL
  計算用的是期貨真實成交價,不是從現貨推算的。持倉過除息吃到的虧損/獲利,跟實盤是一模一樣的。這不是回測偏差,這是真實成本。

  唯一需要注意的小問題是:除息造成的開盤跳空會讓 BB bands/ROC 在前幾根 bar
  產生噪音信號,但這個問題在實盤也一樣存在,不影響回測可信度。

✻ Baked for 1m 3s

> 期貨轉倉(換月) — CSV 是連續資料,近月→次月換倉時也有跳空, 這部分怎麼辦?

● 先看實際資料裡轉倉跳空有多大,再決定要不要處理。

● Bash(cd E:/API/BB_DA/2Axis && python -c "
      import pandas as pd…)
  ⎿  === ��������� gap �έp ===
       ������: 1813
       gap ����: -0.1 pts
     … +35 lines (ctrl+o to expand)
  ⎿  (timeout 30s)

● 資料說話了:

  結算日轉倉 gap vs 一般交易日 gap

  ┌────────────┬────────────────────┬───────────────────────┐
  │            │ 結算日轉倉 (72 次) │ 一般交易日 (1,741 天) │
  ├────────────┼────────────────────┼───────────────────────┤
  │ |gap| 平均 │ 10.2 pts           │ 17.1 pts              │
  ├────────────┼────────────────────┼───────────────────────┤
  │ |gap| P50  │ 1.0 pts            │ 1.0 pts               │
  ├────────────┼────────────────────┼───────────────────────┤
  │ |gap| P90  │ 1.0 pts            │ 40.0 pts              │
  ├────────────┼────────────────────┼───────────────────────┤
  │ |gap| max  │ 517 pts*           │ 1,683 pts             │
  └────────────┴────────────────────┴───────────────────────┘

  結算日轉倉跳空反而比一般交易日小(0.59 倍)。

  72 次轉倉中,P90 只有 1 點 — 幾乎沒有跳空。唯一的 517 pts(2023-01-18 → 01-30)是農曆年連假,不是轉倉問題。

  真正大跳空來自一般交易日的隔夜事件(美股崩盤、地緣政治等),max 1,683 pts,P99 = 300 pts。這些才是持倉過夜的真實風險 —
  回測裡已經正確反映了。

  結論

  轉倉跳空對你的 MXF 資料是 non-issue,不需要做
  back-adjust。你的資料供應商(永豐)做的連續合約銜接很平滑。真正影響回測的是一般隔夜跳空,而這些在實盤也一樣會遇到,回測已正確捕捉。

✻ Baked for 1m 22s

> 用全部參數空間來論, 參數組不論是換月的跳空還是隔夜的跳空..等, 有些參數組得到好處, 有些得到壞處, 次數多了都會散布開來,
  其實參數空間很大, 平均起來就小了, 這種觀點OK嗎?

● 這個觀點是對的,而且你的系統設計剛好有一道天然防線來確保它成立。

  你的推論為什麼正確

  跳空對某組參數是利是弊,取決於那個瞬間的持倉方向,而持倉方向取決於 BB_Mult、ROC_Period、Flat_Thr
  等參數的交互作用在那個時間點產生的信號。這跟參數值本身沒有系統性相關 — BB_Mult=2.0 不會比 2.5 更容易在跳空時做多。

  所以在 1,166,400 組參數裡:
  - 某次跳空讓一半參數賺、一半虧
  - 72 次轉倉 × 1,813 次隔夜 → 效果分散在參數空間裡
  - 對整體參數分佈的統計特性(PF、WR 的分佈)→ 影響趨近於零

  Plateau 檢定是這個論點的關鍵守門員

  唯一的風險是:被選出的 top N 剛好是吃到跳空紅利的幸運兒。

  但 Plateau 26-grid 鄰域檢定正好防住這件事:
  - 假設參數組 A 因為某次跳空多賺 500 pts 而擠進 top
  - 它的鄰居(BB±0.2、ROC±1、Flat±0.02)進出場時機略有不同 → 不會同時吃到同一個跳空紅利
  - 鄰居表現不一致 → Plateau = Fail → 被過濾掉

  真正通過 Plateau 的參數組,績效必須在鄰域內穩定,不能靠單一跳空事件撐起來。

  再加上 WFA 雙重保險

  即使 Plateau 漏網,IS 期間的幸運跳空不會在 OOS 期間重演(不同市場環境、不同跳空時間點)。IS→OOS PF decay 檢定會抓出這種假象。

  結論:你的推論完全成立。大參數空間 + Plateau + WFA = 三層防護,跳空不構成回測可信度問題。

0 comments :

張貼留言

.

.
Related Posts Plugin for WordPress, Blogger...