聊天視窗

量化交易策略設計與實踐:從數據到執行的完整流程 - 第 3 章

第三章:特徵工程與訊號擴充 — 因子模型與深度學習前處理

發布於 2026-02-22 10:31

# 第三章:特徵工程與訊號擴充 — 因子模型與深度學習前處理 > **本章目標**:將前章整理好的日線/1‑min bar 資料轉化為可直接投入模型的特徵集合,並探索因子模型與深度學習的前處理策略。 ## 3.1 概念回顧 - **因子(Factor)**:可被量化的市場驅動或公司基本面變量,例如動量、價值、波動率。 - **訊號擴充**:透過變換、交互、滯後等方法產生多維特徵,提升模型捕捉非線性關係的能力。 - **深度學習前處理**:標準化、缺失處理、序列切片、窗口化,確保張量輸入符合網路結構。 > **關鍵**:特徵是模型的靈魂,好的特徵可大幅降低模型複雜度,減少過擬合風險。 ## 3.2 傳統因子構造 以下以 S&P 500 成分股為例,示範如何從日線資料中提取多種因子。 | 因子 | 公式 | 代碼範例 | |------|------|----------| | **動量(Momentum)** | 近 20 日回報 | `df['momentum_20'] = df['close'].pct_change(20)` | | **價值(Value)** | 市盈率 | `df['pe_ratio'] = df['close'] / df['eps']` | | **波動率(Volatility)** | 近 20 日標準差 | `df['vol_20'] = df['close'].pct_change().rolling(20).std()` | | **市值(Size)** | 企業市值 | `df['market_cap'] = df['shares_outstanding'] * df['close']` | > **提示**:在計算因子時務必保持時間對齊,並避免未來訊息偏差(如使用 lag 或 shift)。 ### 3.2.1 程式碼實作 python import pandas as pd import numpy as np # 讀取前章產出的 parquet raw = pd.read_parquet('s&p500_daily.parquet') raw = raw.sort_values(['ticker', 'date']) # 基礎回報 raw['ret'] = raw['close'].pct_change() # 動量因子 raw['momentum_20'] = raw.groupby('ticker')['ret'].transform(lambda x: x.rolling(20).sum()) # 波動率因子 raw['vol_20'] = raw.groupby('ticker')['ret'].transform(lambda x: x.rolling(20).std()) # 價值因子(假設 eps 已經加入) raw['pe_ratio'] = raw['close'] / raw['eps'] # 市值 raw['market_cap'] = raw['shares_outstanding'] * raw['close'] # 清理 NA raw.dropna(subset=['momentum_20', 'vol_20', 'pe_ratio', 'market_cap'], inplace=True) ## 3.3 滯後與差分特徵 **滯後特徵**能捕捉過去資訊對未來價格的影響,常用於時間序列預測。 python # 產生 1、3、5 日滯後 lag_days = [1, 3, 5] for lag in lag_days: raw[f'ret_lag_{lag}'] = raw.groupby('ticker')['ret'].shift(lag) # 差分特徵(如波動率差分) raw['vol_diff'] = raw.groupby('ticker')['vol_20'].diff() # 刪除缺失 raw.dropna(subset=[f'ret_lag_{lag}' for lag in lag_days] + ['vol_diff'], inplace=True) > **技巧**:滯後特徵的數量不宜過多,避免特徵維度過高導致稀疏性問題。可透過 PCA 或 L1 正則化降維。 ## 3.4 交叉特徵與多元正則化 交叉特徵是將兩個或多個因子相乘或取交互,以捕捉非線性關係。 python # 交叉動量與波動率 raw['momentum_vol'] = raw['momentum_20'] * raw['vol_20'] # 交叉價值與市值 raw['pe_market'] = raw['pe_ratio'] * raw['market_cap'] ### 3.4.1 正則化 - **L1(Lasso)**:可自動將不重要特徵係數推至 0,實現特徵選擇。 - **L2(Ridge)**:避免係數過大,減少模型對單一特徵的依賴。 > **實務**:在 LightGBM、XGBoost 等樹模型中,正則化可透過 `lambda_l1`、`lambda_l2` 參數直接控制;在線性模型中則使用 `sklearn.linear_model.ElasticNet`。 ## 3.5 深度學習前處理技巧 深度學習模型(RNN、CNN、Transformer)需要特定的輸入格式:張量(batch, seq_len, feature_dim)。以下為常見前處理流程。 ### 3.5.1 資料切片(Windowing) python seq_len = 30 # 30 天窗口 features = ['close', 'ret', 'momentum_20', 'vol_20'] def create_sequences(df, seq_len, features): X, y = [], [] for i in range(len(df) - seq_len): X.append(df[features].iloc[i:i+seq_len].values) y.append(df['ret'].iloc[i+seq_len]) # 下一天回報作為目標 return np.array(X), np.array(y) # 以某支股票為例 ticker = 'AAPL' tick_df = raw[raw['ticker'] == ticker].reset_index(drop=True) X, y = create_sequences(tick_df, seq_len, features) ### 3.5.2 標準化 使用 `StandardScaler` 或 `MinMaxScaler`,確保不同特徵維度相近。 python from sklearn.preprocessing import StandardScaler scaler = StandardScaler() # 構造 scaler 於整體資料(除目標外) scaler.fit(raw[features]) raw[features] = scaler.transform(raw[features]) ### 3.5.3 資料平衡 若目標為「買/賣」類別,需處理類別不平衡: - **過採樣**:SMOTE - **欠採樣**:RandomUnderSampler - **類別權重**:在損失函數中設定類別權重。 ## 3.6 量化管道整合 將前處理流程封裝為可重用的函式或類別,並利用 `mlflow` 或 `mlflow-sklearn` 追蹤模型與特徵版本。 python from pathlib import Path import mlflow import mlflow.sklearn mlflow.set_experiment('factor_model_pipeline') with mlflow.start_run(): # 1. 讀取資料 data = pd.read_parquet('s&p500_daily.parquet') # 2. 進行特徵工程(上述所有步驟) # 3. 拆分訓練/驗證/測試 # 4. 訓練模型(如 XGBoost) # 5. 追蹤參數、指標 mlflow.log_params({'seq_len': seq_len, 'lags': lag_days}) mlflow.log_metrics({'mae': mae, 'rmse': rmse}) mlflow.sklearn.log_model(model, 'model') > **最佳實務**:使用 `Delta Lake` 或 `Parquet` 版本控制特徵表;在每一次特徵變更時產生新的資料檔,並在 MLflow 中標註 `artifact_uri`。 ## 小結 - **特徵工程**:因子構造、滯後、交叉、正則化是提升模型表現的關鍵; - **深度學習前處理**:序列切片、標準化、資料平衡能確保張量輸入的品質; - **管道管理**:將整個流程自動化、版本化,才能在實盤環境中快速迭代。 > **行動項**:在下一章「回測與優化」之前,先完成上述特徵工程並將特徵表輸出為 `s&p500_features.parquet`。確保每個因子都有說明文件與統計摘要,方便後續模型選擇。