📋 程式概述
這是一個企業級股票數據清洗與時間週期轉換系統,專門處理日 K 線數據並轉換為週/月/年 K 線,同時進行多層次的數據品質控管與異常偵測。程式採用玩股網口徑標準,確保數據品質符合量化交易需求。
🎯 核心功能架構
1. 數據來源與處理範圍
- 時間範圍:2000-01-01 ~ 2025-12-31(可調整)
- 緩衝機制:起始日前保留 14 天數據,確保第一期有前收盤價可計算
- 市場支援:台股、美股、港股、日股、韓股、陸股
- 處理模式:3 種寫入模式(本地 → Drive 移動/複製/直接寫入)
🧹 數據清洗機制(12 大類別)
一、基礎價量檢查 (_basic_price_checks)
| 檢查項目 | 條件說明 | 目的與備註 |
|--------------|------------------------------------------------|-------------------------------|
| 價格正值 | 開 / 高 / 低 / 收 > 0 | 排除負值或零價格 |
| OHLC 邏輯 | 最高 ≥ 開 / 收 / 低<br>最低 ≤ 開 / 收 / 高 | 確保價格關係合理 |
| 成交量(可選)| 成交量 > 0(預設允許零量,可配置) | 避免停牌或無效資料 |
| 日期有效性 | 年份 ≥ 1990 | 排除異常年份 |
| 時區處理 | 統一移除時區(tz_localize(None)) | 避免跨時區計算錯誤 |
# 範例:排除不合理價格
df = df[(df['最高'] >= df[['開盤','收盤','最低']].max(axis=1))]
二、極端報酬過濾 (_extreme_return_filter_day)
🚨 日 K 極端值門檻
| 報酬類型 | 門檻值 | 計算公式 | 說明 |
|------------------|--------|----------------------|----------------------------|
| Ret_Gap | ±100% | 開盤 / 前收 - 1 | 跳空報酬(Gap) |
| Ret_Candle | ±100% | 收盤 / 開盤 - 1 | 當日漲跌(Candle) |
| Ret_Max_FromP | ±100% | 最高 / 前收 - 1 | 最高價相對前收報酬 |
| Ret_Min_FromP | ±100% | 最低 / 前收 - 1 | 最低價相對前收報酬 |
過濾邏輯:
# 任一報酬 > 100% 即視為異常(可能是除權、拆股未調整)
mask_ok = (
(ret_gap.abs() < 100) &
(ret_candle.abs() < 100) &
(ret_max_p.abs() < 100) &
(ret_min_p.abs() < 100)
)
三、Ghost 數據偵測與刪除
什麼是 Ghost 數據?
- 定義:四價相等(開=高=低=收)且成交量為 0 的占位數據
- 來源:交易所在停牌期間填充的假數據
- 處理:自動識別並刪除
Ghost 段落偵測規則
| 參數名稱 | 設定值 | 說明 |
|----------------|---------|--------------------------------------------|
| GHOST_MIN_LEN | 2 天 | 連續至少 2 天 ghost 才算占位段 |
| EPS_EQ | 1e-8 | 四價相等容許誤差(開高低收差距 ≤ 1e-8) |
# Ghost 判定邏輯
ghost_mask = (成交量 == 0) & (開=高=低=收)
四、停牌/復牌偵測 (_mark_resume_flags_with_ghost)
🎯 復牌標記條件(3 層偵測)
條件 A:長期停牌後復牌
停牌間隔 ≥ 5 天 且 開盤跳空 ≥ 30%
條件 B:Ghost 段落前極端跳空
Ghost 連續 ≥ 2 天 且 Ghost 前 5 天內有跳空 ≥ 30%
條件 C:極端日報酬(新增強化)
日報酬絕對值 > 100%(股價翻倍或腰斬)
參數配置:
| 參數名稱 | 設定值 | 說明 |
|-----------------------|----------|--------------------------------------|
| LONG_GAP_DAYS | 5 天 | 停牌判定天數(與前一筆相隔 ≥ 5 天) |
| RESUME_JUMP_THRESHOLD | 30% | 復牌跳空門檻(開盤/前收 ±30%) |
| PRE_RESUME_DAYS | 5 天 | Ghost 段前回溯天數 |
| DAILY_JUMP_THRESHOLD | 100% | 極端日報酬門檻(收盤/前收 ±100%) |
五、週 K 過濾機制(3 種規則)
規則 1:玩股網口徑(上週無交易 + 大跳空)
條件:上週交易天數 = 0 且 本週跳空 > 80%
結果:本週報酬設為 NaN
應用場景: 長假後復牌、IPO 首週
規則 2:復牌事件極端值過濾
| 條件類型 | 判斷邏輯說明 | 過濾目的 |
|------------------|------------------------------------------------------------|------------------------------|
| 極端事件當週 | HasResume = 1 且 `Ret_Trad` 絕對值 > 100% | 排除復牌週的極端報酬干擾 |
| 極端事件次週 | 上週為極端事件 → 本週一併排除 | 避免連續干擾與統計偏誤 |
| 薄樣本極端值 | HasResume = 1 且(週內交易日數 < 3 或 `Ret_Trad` > 50%) | 排除復牌後樣本不足的異常週期 |
規則 3:報酬異常值處理
# 週報酬過濾後設為 NaN
cols_to_nan = ['Ret_Trad_W', 'Ret_Max_H_W', 'Ret_Min_L_W']
六、月/年 K 穩健過濾(3 大條件)
條件 A:覆蓋率不足(新掛牌/殘年)
| 週期類型 | 最低交易日數 | 說明 |
|----------|---------------|--------------------------------------------|
| 月 K | 5 天 | 若低於 5 天,視為樣本不足,不納入統計 |
| 年 K | 120 天 | 約半年交易日,避免新股首年干擾 |
條件 B:停牌/復牌極端值
過濾邏輯:
HasResume = 1 且 (
週期交易日 < 3 或
|Ret_Trad| > 50% 或
|Ret_Gap| > 50%
)
條件 C:上期無交易 + 本期大跳空
上期交易日 = 0 且 |Ret_Gap| > 80%
QA 追蹤機制:
- 月/年 K 新增
IsFiltered_QA欄位 IsFiltered_QA = 1表示該行報酬被過濾為 NaN- 自動輸出過濾清單到
_qa/monthly_filtered_summary.csv
七、Ping-Pong 除權錯位偵測
什麼是 Ping-Pong 模式?
連續兩天出現大幅反向波動,可能是除權日期標記錯誤。
偵測規則
條件:
前一日漲跌幅 > 40% 且
次一日漲跌幅 > 40% 且
兩日方向相反(一漲一跌)
處理方式:
- 偵測到 Ping-Pong 模式 → 跳過該股票
- 輸出疑似清單到
_qa/skip_pingpong.csv
# 範例:2330 在某日出現 +45% → -43%
異常日期: 2024-03-15
前日收盤: 100 → 當日收盤: 145 → 次日收盤: 82.65
八、時區邊界修正(月份歸屬錯誤)
🐛 問題案例
修正前:
日期: 2024-09-01 08:00 (UTC+8)
轉換後被歸入: 8 月
修正後:
# 統一處理時區
df['日期'] = pd.to_datetime(df['日期'])
if df['日期'].dt.tz is not None:
df['日期'] = df['日期'].dt.tz_convert('Asia/Taipei').dt.tz_localize(None)
影響範圍: 月/年 K 聚合、週末日判定
📊 數據轉換功能
1. OHLC 聚合規則
週 K(W-FRI)
規則:以「週五」為週末日(與玩股網一致)
開盤:週內第一個交易日的開盤價
最高:週內最高價
最低:週內最低價
收盤:週內最後一個交易日的收盤價
成交量:週內成交量加總
月 K(ME)/ 年 K(YE)
規則:以月末/年末為結算日
聚合邏輯:同週 K
額外欄位:
- CurPeriod_Days: 本期交易日數
- PrevPeriod_Days: 上期交易日數
- HasResume: 本期是否發生復牌事件
2. 報酬率計算(12 種指標)
| 指標名稱 | 計算公式 | 說明 |
|------------------|------------------------------|------------------------------|
| Ret_Gap | (開盤 / 前收) - 1 | 跳空報酬 |
| Ret_Trad | (收盤 - 前收) / 前收 | 交易報酬(完整週期) |
| Ret_C | (收盤 - 開盤) / 開盤 | 盤中報酬 |
| Ret_Max_H | (最高 - 前收) / 前收 | 最大報酬 |
| Ret_Min_L | (最低 - 前收) / 前收 | 最小報酬 |
| Range | (最高 - 最低) / 開盤 | 振幅 |
| Ret_Max_H_Pos | Ret_Max_H.clip(lower=0) | 正向最大報酬(負值設為 0) |
| Ret_H_from_O | (最高 - 開盤) / 開盤 | 最高相對開盤 |
| Ret_L_from_O | (最低 - 開盤) / 開盤 | 最低相對開盤 |
| Ret_End_RelH | (收盤 - 最高) / 最高 | 收盤相對最高 |
| Ret_End_RelL | (收盤 - 最低) / 最低 | 收盤相對最低 |
| PrevC | 前一期收盤價 | 基準價格(報酬計算依據) |
🔍 QA 品質稽核(7 大報表)
QA 1:週收盤報酬分布分析
檔案: weekly_distribution_summary.csv
功能:
- 將週報酬分 25 個區間(-1000% ~ +10000%)
- 統計每週各區間股票數量占比
- 滾動 26 週計算 Z-Score
異常警報觸發條件:
- 任一區間 Z-Score > 5
- 極端尾部(±100%)Z-Score > 3
QA 2:週報酬漂移警報
檔案: weekly_drift_alerts.csv
偵測邏輯:
- 當週分布相對過去 26 週基準線偏離過大
- 極端尾部占比異常增加
輸出範例:
ISO_Week,reason,max_abs_z
2024-12,distribution_drift,7.3
2024-25,distribution_drift,5.8
QA 3:週 vs 日幾何連乘驗證
檔案: weekly_vs_daily_diff.csv
驗證邏輯:
週報酬 (Ret_Trad_W) vs. 週內日報酬幾何連乘
diff = geom_chain - Ret_Trad_W
品質指標:
- 中位數差異 < 0.001(0.1%)
- |diff| > 0.05 的比例 < 1%
QA 4:週高一致性檢查
檔案: weekly_high_mismatch.csv
檢查項目:
週K最高價 vs. 週內日K最高價的最大值
異常條件:
|週K最高 - 週內日高max| > 1e-6
QA 5:週高報酬厚尾分析
檔案: weekly_top_outliers_high.csv
功能:
- 分析 Ret_Max_H_W(最高相對前收)的極端值
- 每週輸出前 200 大離群值
- 標記可能的數據錯誤
QA 6:週 K 過濾統計
檔案: weekly_nan_filtered_summary.csv
統計項目:
- 有多少週報酬被過濾為 NaN
- 每檔股票被過濾的週數
輸出範例:
StockID,Filtered_Count
2330,3
2454,5
QA 7:月/年 K 過濾統計
檔案:
monthly_filtered_summary.csvyearly_filtered_summary.csv
統計項目:
- 基於 IsFiltered_QA = 1 標記
- 每檔股票被過濾的期數
過濾原因:
1. 覆蓋率不足(交易日 < 門檻)
2. 停牌/復牌極端值
3. 上期無交易 + 本期大跳空
⚙️ 性能優化設計
1. 多執行緒並行處理
參數:MAX_WORKERS = 16
策略:每個 CSV 檔案獨立處理
加速效果:處理 2000 檔股票約 3-5 分鐘
2. 本地快取機制
流程:
1. Drive dayK → 本地 /content/_wmy_tmp/dayK_cache
2. 增量複製(只複製新增/修改檔案)
3. 處理完成後可保留快取
優勢:
- 避免重複下載(節省 70% 時間)
- 網路不穩時有備份
- 支援離線重新處理
3. 批次合併寫入
SHARD_SIZE = 10,000 # 每批處理 10,000 檔
ROW_GROUP_SIZE = 512,000 # Parquet 行組大小
效果:
- 減少磁碟 I/O 次數
- 降低記憶體峰值
- 提升 Parquet 壓縮率
4. 串流寫入 Parquet
class ParquetStreamer:
# 邊處理邊寫入,不需累積全部數據到記憶體
def append_df(self, df):
self.writer.write_table(table, row_group_size=...)
📁 輸出檔案結構
各國股票檔案/
├── tw-share/
│ ├── dayK/ # 原始日K CSV
│ ├── weekK_TW.parquet # 週K (帶 ISO_Week)
│ ├── monthK_TW.parquet # 月K (帶 IsFiltered_QA)
│ ├── yearK_TW.parquet # 年K (帶 IsFiltered_QA)
│ └── _qa/ # QA 報表資料夾
│ ├── skip_pingpong.csv # Ping-Pong 略過清單
│ ├── weekly_distribution_summary.csv
│ ├── weekly_drift_alerts.csv
│ ├── weekly_top_outliers.csv
│ ├── weekly_vs_daily_diff.csv
│ ├── weekly_high_mismatch.csv
│ ├── weekly_distribution_summary_high.csv
│ ├── weekly_top_outliers_high.csv
│ ├── weekly_nan_filtered_summary.csv
│ ├── monthly_filtered_summary.csv
│ └── yearly_filtered_summary.csv
🎓 適用場景
1. 量化交易回測
# 使用清洗後的週K進行策略回測
weekK = pd.read_parquet('weekK_TW.parquet')
# Ret_Trad_W 已過濾極端值,可直接用於計算策略報酬
2. 風險控管
# 識別高風險標的
high_risk = weekK[weekK['HasResume'] > 0]
# 避開停牌復牌標的
3. 數據品質監控
# 定期檢查 QA 報表
alerts = pd.read_csv('_qa/weekly_drift_alerts.csv')
if len(alerts) > 0:
send_email_alert(alerts)
4. 學術研究
# 排除異常數據的實證研究
clean_data = monthK[monthK['IsFiltered_QA'] == 0]
🔧 關鍵參數配置表
| 類別 | 參數名稱 | 預設值 | 調整建議或說明 |
|--------------|------------------------|---------------|---------------------------------------------|
| 時間範圍 | DATE_START | 2000-01-01 | 可依需求調整起始日期 |
| | DATE_END | 2025-12-31 | 建議設為最新資料日期 |
| | PAD_DAYS | 14 | 起始緩衝,建議 7–30 天 |
| 極端報酬 | EXTREME_RET_GAP | 100% | 台股:50–100%;美股:100–200% |
| | DAILY_JUMP_THRESHOLD | 100% | 復牌極端報酬門檻 |
| 停牌偵測 | LONG_GAP_DAYS | 5 | 停牌判定天數(與前一筆相隔 ≥ 5 天) |
| | RESUME_JUMP_THRESHOLD | 30% | 復牌跳空門檻(開盤/前收 ±30%) |
| 週K過濾 | STOPWEEK_JUMP_THRESHOLD| 80% | 玩股口徑門檻(跳空 ±80%) |
| | BIG_RET_THRESHOLD | 50% | 復牌薄樣本門檻(報酬 ±50%) |
| 月/年K過濾 | MIN_DAYS_MONTH | 5 | 月K最低交易日數 |
| | MIN_DAYS_YEAR | 120 | 年K最低交易日數(約半年) |
| | BIG_RET_MONTH_YEAR | 50% | 月/年K極端報酬門檻 |
| Ping-Pong偵測| PINGPONG_THRESHOLD | 40% | 除權錯位偵測門檻 |
| 性能設定 | MAX_WORKERS | 16 | 建議依 CPU 核心數調整 |
| | SHARD_SIZE | 10,000 | 記憶體 16GB:5–10k;32GB:10–15k |
| | ROW_GROUP_SIZE | 512,000 | Parquet 優化建議值 |
✨ 技術亮點總結
| 特色項目 | 說明內容 |
|----------------------|--------------------------------------------------------|
| ✅ 12 層數據清洗 | 從基礎價量到復牌事件,涵蓋 ghost、極端報酬、樣本過濾等 |
| ✅ 3 級復牌偵測 | 長停牌 + ghost 段落 + 極端跳空,多層邏輯交叉判定 |
| ✅ 週期適應過濾 | W/M/Y 各自設計過濾邏輯,避免統一門檻造成誤殺 |
| ✅ 時區邊界修正 | 解決跨時區造成的月份歸屬錯誤,統一為本地時間 |
| ✅ 7 大 QA 報表 | 分布 / 漂移 / 一致性等指標,支援回測品質監控 |
| ✅ 增量快取機制 | 快取已下載檔案,節省約 70% 重複下載時間 |
| ✅ 批次串流寫入 | Parquet 串流寫入,低記憶體峰值處理大數據 |
| ✅ 玩股網口徑 | 週K結算日與跳空邏輯符合業界標準 |
| ✅ IsFiltered_QA 標記 | 可追溯每筆被過濾的原因,支援 QA 統計與回測排除 |
| ✅ Ping-Pong 偵測 | 自動識別除權錯位,排除疑似異常報酬週期 |
🚨 注意事項
數據品質提醒
- 過濾後報酬為 NaN
- 週 K: Ret_Trad_W = NaN 表示該週報酬不可信
- 月/年 K: IsFiltered_QA = 1 表示該期被過濾
- 復牌事件處理
- HasResume = 1 的週/月/年建議額外注意
- 可能伴隨極端報酬(即使未被過濾為 NaN)
- 新掛牌股票
- 首月/首年可能因交易日不足被過濾
- 建議至少上市滿 1 年後再納入回測
- 時區敏感場景
- 跨國市場比較需注意時區統一
- 月末日判定已處理台灣時區(UTC+8)
性能調優建議
- 記憶體不足時
- 降低 SHARD_SIZE 至 3,000-5,000
- 降低 MAX_WORKERS 至 8
- 速度優化
- SSD 硬碟可提升 SHARD_SIZE 至 15,000
- 增加 MAX_WORKERS 至 CPU 核心數的 1.5 倍
- Colab 環境
- 使用 WRITE_MODE = 'B' 避免 Drive 直寫慢
- 定期清理 /content/_wmy_tmp 避免空間不足