返回目錄
A
量化交易策略設計與實踐:從數據到執行的完整流程 - 第 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`。確保每個因子都有說明文件與統計摘要,方便後續模型選擇。